WordPress
Di halaman ini
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
- Halaman beranda yang mendaftar pos terbaru
/blog— arsip lengkap setiap pos/blog/[slug]— satu halaman HTML statis per pos (tetap jalan pada ribuan pos)fetchWithCacheagar build ulang memakai kembali respons WP alih-alih mengunduh semuanya lagi
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.cssexport 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.com2. 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 { 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</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Terbaru dari blog</h1>
<ul>
${data.posts
.map(
(post) => `
<li>
<a href="${postPath(post)}">${post.title.rendered}</a>
</li>`,
)
.join('')}
</ul>
<p><a href="/blog">Semua tulisan</a></p>
</body>
</html>
`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')),
),
)import { getPosts, postPath } from './lib/wordpress.js'
export async function data() {
const posts = await getPosts({ perPage: 5 })
return { posts }
}
export default function Home({ data }) {
return (
<html lang="id">
<head>
<title>Situs saya</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<h1>Terbaru dari blog</h1>
<ul>
{data.posts.map((post) => (
<li key={post.slug}>
<a href={postPath(post)}>{post.title.rendered}</a>
</li>
))}
</ul>
<p><a href="/blog">Semua tulisan</a></p>
</body>
</html>
)
}4. Indeks blog
Gunakan getAllPosts() agar arsipnya tidak terbatas pada 50–100 item.
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</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Blog (${data.posts.length})</h1>
<ul>
${data.posts
.map(
(post) => `
<li>
<a href="${postPath(post)}">${post.title.rendered}</a>
<time>${post.date.slice(0, 10)}</time>
</li>`,
)
.join('')}
</ul>
</body>
</html>
`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)),
),
),
),
),
)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 function BlogIndex({ data }) {
return (
<html lang="id">
<head>
<title>Blog</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<h1>Blog ({data.posts.length})</h1>
<ul>
{data.posts.map((post) => (
<li key={post.slug}>
<a href={postPath(post)}>{post.title.rendered}</a>
<time>{post.date.slice(0, 10)}</time>
</li>
))}
</ul>
</body>
</html>
)
}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 {
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}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<article>
<p><a href="/blog">← Blog</a></p>
<h1>${post.title.rendered}</h1>
<time>${post.date.slice(0, 10)}</time>
${image ? `<img src="${image}" alt="">` : ''}
<div class="content">
${post.content.rendered}
</div>
</article>
</body>
</html>
`
}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),
),
),
)
}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 function BlogPost({ data }) {
const { post } = data
const image = featuredImage(post)
return (
<html lang="id">
<head>
<title>{post.title.rendered}</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<article>
<p><a href="/blog">← Blog</a></p>
<h1>{post.title.rendered}</h1>
<time>{post.date.slice(0, 10)}</time>
{image ? <img src={image} alt="" /> : null}
<div
className="content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
/>
</article>
</body>
</html>
)
}6. Build
WP_URL=https://your-wordpress-site.com sitelo buildBuild 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