WordPress

WordPress’i başsız bir CMS gibi kullanın ve bütün siteyi çekin: /wp-json/wp/v2/posts üzerinde sayfalayın, slug başına bir HTML dosyası üretin ve API yanıtlarını derlemeler arasında önbelleğe alın.

Elinize geçenler

Proje düzeni

my-site/
  sitelo.config.js
  src/
    lib/
      wordpress.js       # WP REST yardımcıları (sayfalı çekim)
    index.ht.js          # ana sayfa — son yazıları listele
    blog/
      index.ht.js        # /blog — tam arşiv
      [slug].ht.js       # /blog/:slug — her yazı
    css/
      styles.css
export default {
  site: 'https://example.com',
  // binlerce sayfa mı? eşzamanlılığı artır
  renderConcurrency: 16,
  renderBatchSize: 128,
}

1. WordPress sitenizi gösterin

REST API modern WordPress’te varsayılan olarak açıktır. https://your-site.com/wp-json/wp/v2/posts adresinden doğrulayın.

Ortamda WP_URL değerini ayarlayın (ya da denerken doğrudan koda yazın):

WP_URL=https://your-wordpress-site.com

2. Paylaşılan WordPress yardımcıları

getAllPosts(), X-WP-TotalPages başlığını okur ve her sayfayı gezer (WordPress per_page değerini 100 ile sınırlar). Slugları toplarken _embed kullanmayın — gömülüleri yalnızca tek tek yazılar için getirin.

import { fetchWithCache } from 'sitelo'

const WP_URL = process.env.WP_URL ?? 'https://your-wordpress-site.com'
const PER_PAGE = 100 // /wp/v2/posts için WP üst sınırı

async function wpFetch(path, query = {}) {
  const url = new URL(`/wp-json/wp/v2${path}`, WP_URL)
  for (const [key, value] of Object.entries(query)) {
    if (value != null) url.searchParams.set(key, String(value))
  }

  const res = await fetchWithCache(url, undefined, {
    maxAge: 3600,
    cache: 'auto',
  })

  if (!res.ok) {
    throw new Error(`WordPress ${res.status}: ${url}`)
  }

  return {
    data: await res.json(),
    totalPages: Number(res.headers.get('X-WP-TotalPages') ?? 1),
    total: Number(res.headers.get('X-WP-Total') ?? 0),
  }
}

export async function getPosts({ page = 1, perPage = 20, embed = true } = {}) {
  const { data } = await wpFetch('/posts', {
    page,
    per_page: perPage,
    _embed: embed ? '1' : undefined,
  })
  return data
}

/** Site tamamen çekilene dek /posts’un her sayfasında yürü. */
export async function getAllPosts({
  perPage = PER_PAGE,
  embed = false,
  onPage,
} = {}) {
  const first = await wpFetch('/posts', {
    page: 1,
    per_page: perPage,
    _embed: embed ? '1' : undefined,
  })

  const posts = [...first.data]
  onPage?.(1, first.totalPages, posts.length)

  for (let page = 2; page <= first.totalPages; page += 1) {
    const next = await wpFetch('/posts', {
      page,
      per_page: perPage,
      _embed: embed ? '1' : undefined,
    })
    posts.push(...next.data)
    onPage?.(page, first.totalPages, posts.length)
  }

  return posts
}

export async function getPostBySlug(slug) {
  const { data } = await wpFetch('/posts', {
    slug,
    _embed: '1',
  })
  return data[0] ?? null
}

export function postPath(post) {
  return `/blog/${post.slug}`
}

export function featuredImage(post) {
  return post._embedded?.['wp:featuredmedia']?.[0]?.source_url
}

3. Ana sayfa

import { html, head, title, link, body, h1, ul, li, a, p } from 'javascript-to-html'
import { getPosts, postPath } from './lib/wordpress.js'

export async function data() {
  const posts = await getPosts({ perPage: 5 })
  return { posts }
}

export default ({ data }) =>
  html({ lang: 'tr' },
    head(
      title('Sitem'),
      link({ rel: 'stylesheet', href: '/styles.css' }),
    ),
    body(
      h1('Blogdan son çıkanlar'),
      ul(
        ...data.posts.map((post) =>
          li(a({ href: postPath(post) }, post.title.rendered)),
        ),
      ),
      p(a({ href: '/blog' }, 'Tüm yazılar')),
    ),
  )

4. Blog dizini

Arşiv 50–100 öğeyle sınırlanmasın diye getAllPosts() kullanın.

import { html, head, title, link, body, h1, ul, li, a, time } from 'javascript-to-html'
import { getAllPosts, postPath } from '../lib/wordpress.js'

export async function data() {
  // Tam arşiv — tüm WP sitesinde sayfa sayfa ilerle
  const posts = await getAllPosts({ embed: false })
  return { posts }
}

export default ({ data }) =>
  html({ lang: 'tr' },
    head(
      title('Blog'),
      link({ rel: 'stylesheet', href: '/styles.css' }),
    ),
    body(
      h1(`Blog (${data.posts.length})`),
      ul(
        ...data.posts.map((post) =>
          li(
            a({ href: postPath(post) }, post.title.rendered),
            time(post.date.slice(0, 10)),
          ),
        ),
      ),
    ),
  )

5. Her yazıyı statik sayfalara çekin

generateStaticParams, dist/ içinde istediğiniz her slug’ı döndürmelidir. API’yi burada sayfalayın — getPosts({ perPage: 100 }) çağrısını bir kez yapıp durmayın.

import {
  html, head, title, link, body, article, p, a, h1, time, img, div,
} from 'javascript-to-html'
import {
  getAllPosts,
  getPostBySlug,
  featuredImage,
} from '../lib/wordpress.js'

export async function generateStaticParams() {
  const ripStarted = performance.now()
  console.log(
    `[wordpress] yazılar çekiliyor… (${process.uptime().toFixed(1)}s başlangıçtan beri)`,
  )

  // Yayımlanmış her yazıyı çek (binlercesi sorun değil — istek başına 100)
  const posts = await getAllPosts({
    embed: false, // yalnızca slug; hız için _embed’i atla
    onPage: (page, totalPages, count) => {
      console.log(`[wordpress] sayfa ${page}/${totalPages} (${count} yazı)`)
    },
  })

  const ripSeconds = ((performance.now() - ripStarted) / 1000).toFixed(1)
  console.log(
    `[wordpress] çekildi ${posts.length} yazı süre: ${ripSeconds}s` +
      ` (${process.uptime().toFixed(1)}s başlangıçtan beri)`,
  )

  return posts.map((post) => ({ slug: post.slug }))
}

export async function data({ params }) {
  const post = await getPostBySlug(params.slug)
  if (!post) throw new Error(`Yazı bulunamadı: ${params.slug}`)
  return { post }
}

export default ({ data }) => {
  const { post } = data
  const image = featuredImage(post)

  return html({ lang: 'tr' },
    head(
      title(post.title.rendered),
      link({ rel: 'stylesheet', href: '/styles.css' }),
    ),
    body(
      article(
        p(a({ href: '/blog' }, '← Blog')),
        h1(post.title.rendered),
        time(post.date.slice(0, 10)),
        image ? img({ src: image, alt: '' }) : '',
        div({ class: 'content' }, post.content.rendered),
      ),
    ),
  )
}

6. Derleme

WP_URL=https://your-wordpress-site.com sitelo build

İlk derleme WordPress üzerinden bir kez sayfalar ve getirme önbelleğini doldurur. Sonraki derlemeler maxAge dolana dek önbelleğe alınmış liste/ayrıntı yanıtlarını yeniden kullanır (cache: 'auto' → üretimde dosya sistemi). Binlerce yazı sayfası işliyorsanız sitelo.config.js içindeki renderConcurrency değerini yükseltin.

Notlar

WordPress’ten gelen HTML

title.rendered ve content.rendered WP’den gelen HTML dizeleridir. Onları şablonunuza olduğu gibi bırakın (yukarıdaki gibi) ya da CMS’e tümüyle güvenmiyorsanız temizleyin.

Özel içerik

Genel REST rotaları yalnızca yayımlanmış yazıları açar. Taslaklar ya da özel kimlik doğrulama için başlıkları fetchWithCache fonksiyonunun ikinci argümanına (standart fetch init) geçirin ve kararlı bir cacheKey kullanın.

Veri yükleme belgeleri · Yönlendirme belgeleri