WordPress

Perlakukan WordPress sebagai CMS headless dan tarik seluruh situsnya: telusuri halaman demi halaman /wp-json/wp/v2/posts, hasilkan satu berkas HTML per slug, dan singgahkan respons API antarbuild.

Yang Anda dapat

Tata letak proyek

my-site/
  sitelo.config.js
  src/
    lib/
      wordpress.js       # pembantu REST WP (unduh bertahap)
    index.ht.js          # beranda — daftar tulisan terbaru
    blog/
      index.ht.js        # /blog — arsip lengkap
      [slug].ht.js       # /blog/:slug — setiap tulisan
    css/
      styles.css
export default {
  site: 'https://example.com',
  // ribuan halaman? naikkan konkurensi
  renderConcurrency: 16,
  renderBatchSize: 128,
}

1. Arahkan ke situs WordPress Anda

REST API menyala secara bawaan di WordPress modern. Pastikan di https://your-site.com/wp-json/wp/v2/posts.

Setel WP_URL di lingkungan (atau tulis langsung selagi bereksperimen):

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

2. Pembantu WordPress bersama

getAllPosts() membaca X-WP-TotalPages dan menyusuri setiap halaman (WordPress membatasi per_page pada 100). Lewati _embed selagi mengumpulkan slug — ambil sematan hanya untuk pos satuan.

import { fetchWithCache } from 'sitelo'

const WP_URL = process.env.WP_URL ?? 'https://your-wordpress-site.com'
const PER_PAGE = 100 // batas WP untuk /wp/v2/posts

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
}

/** Telusuri setiap halaman /posts sampai seluruh situs terunduh. */
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. Halaman beranda

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: 'id' },
    head(
      title('Situs saya'),
      link({ rel: 'stylesheet', href: '/styles.css' }),
    ),
    body(
      h1('Terbaru dari blog'),
      ul(
        ...data.posts.map((post) =>
          li(a({ href: postPath(post) }, post.title.rendered)),
        ),
      ),
      p(a({ href: '/blog' }, 'Semua tulisan')),
    ),
  )

4. Indeks blog

Gunakan getAllPosts() agar arsipnya tidak terbatas pada 50–100 item.

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() {
  // Arsip lengkap — telusuri seluruh situs WP halaman demi halaman
  const posts = await getAllPosts({ embed: false })
  return { posts }
}

export default ({ data }) =>
  html({ lang: 'id' },
    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. Tarik setiap pos menjadi halaman statis

generateStaticParams harus mengembalikan setiap slug yang Anda inginkan di dist/. Telusuri API-nya halaman demi halaman di sini — jangan memanggil getPosts({ perPage: 100 }) sekali lalu berhenti.

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] mengunduh tulisan… (${process.uptime().toFixed(1)}s sejak mulai)`,
  )

  // Unduh setiap tulisan terbit (ribuan pun tidak masalah — 100 per permintaan)
  const posts = await getAllPosts({
    embed: false, // slug saja; lewati _embed demi kecepatan
    onPage: (page, totalPages, count) => {
      console.log(`[wordpress] halaman ${page}/${totalPages} (${count} tulisan)`)
    },
  })

  const ripSeconds = ((performance.now() - ripStarted) / 1000).toFixed(1)
  console.log(
    `[wordpress] terunduh ${posts.length} tulisan dalam ${ripSeconds}s` +
      ` (${process.uptime().toFixed(1)}s sejak mulai)`,
  )

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

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

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

  return html({ lang: 'id' },
    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. Build

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

Build pertama menelusuri WordPress sekali dan mengisi singgahan pengambilan. Build berikutnya memakai kembali respons daftar/detail yang disinggahkan (cache: 'auto' → sistem berkas di produksi) sampai maxAge kedaluwarsa. Naikkan renderConcurrency di sitelo.config.js jika Anda merender ribuan halaman pos.

Catatan

HTML dari WordPress

title.rendered dan content.rendered adalah string HTML dari WP. Masukkan apa adanya ke templat Anda (seperti di atas), atau bersihkan bila Anda tidak sepenuhnya memercayai CMS-nya.

Konten privat

Rute REST publik hanya menampilkan pos yang sudah terbit. Untuk draf atau autentikasi khusus, teruskan header ke argumen kedua fetchWithCache (init fetch standar) dan gunakan cacheKey yang tetap.

Dokumentasi pemuatan data · Dokumentasi perutean