Static Site Generation
WPNuxt supports full static site generation, pre-rendering all pages at build time. This produces static HTML files that can be deployed to any CDN or static hosting provider.
When to Choose SSG
| Consideration | SSG | SSR |
|---|---|---|
| Content updates | Requires rebuild | Instant (with SWR cache) |
| Hosting | Any static host, no server needed | Requires Node.js |
| Performance | Fastest TTFB (~10-50ms from CDN) | Fast with caching (~50-200ms) |
| Build time | Grows with content volume | N/A |
| Preview support | Limited | Full support |
| Cost | Cheapest (static hosting) | Depends on compute |
Choose SSG for blogs, documentation, and marketing sites where content changes infrequently and you can trigger rebuilds on publish.
Choose SSR for sites with many pages, frequent updates, preview requirements, or authenticated content.
Configuration
Set the Nitro preset to static and configure pre-rendering:
export default defineNuxtConfig({
modules: ['@wpnuxt/core'],
nitro: {
preset: 'static',
prerender: {
concurrency: 10, // Parallel route fetches
interval: 1000, // Delay between batches (avoid overwhelming WordPress)
failOnError: false, // Don't fail build on individual route errors
routes: ['/'], // Seed route for crawling
},
},
wpNuxt: {
wordpressUrl: 'https://your-wordpress.com',
},
})
Pre-rendering WordPress Routes
Nuxt's crawler discovers routes by following links from the seed routes. For WordPress sites, it's more reliable to fetch all routes explicitly using the prerender:routes hook:
const WORDPRESS_URL = 'https://your-wordpress.com'
const GRAPHQL_ENDPOINT = '/graphql'
export default defineNuxtConfig({
modules: ['@wpnuxt/core'],
nitro: {
preset: 'static',
prerender: {
concurrency: 10,
interval: 1000,
failOnError: false,
routes: ['/'],
},
},
hooks: {
async 'prerender:routes'(ctx) {
await fetchWordPressRoutes(ctx.routes)
},
},
wpNuxt: {
wordpressUrl: WORDPRESS_URL,
},
})
async function fetchWordPressRoutes(routes: Set<string>) {
console.log('[wpnuxt] Fetching WordPress routes for prerendering...')
try {
const response = await fetch(`${WORDPRESS_URL}${GRAPHQL_ENDPOINT}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query AllContentForPrerender {
posts(first: 100) { nodes { uri } }
pages(first: 100) { nodes { uri } }
}
`,
}),
})
const data = await response.json() as {
data?: {
posts?: { nodes: Array<{ uri: string }> }
pages?: { nodes: Array<{ uri: string }> }
}
}
const posts = data.data?.posts?.nodes || []
const pages = data.data?.pages?.nodes || []
for (const post of posts) {
if (post.uri) routes.add(post.uri)
}
for (const page of pages) {
if (page.uri) routes.add(page.uri)
}
console.log(`[wpnuxt] Added ${posts.length} posts and ${pages.length} pages`)
} catch (error) {
console.warn('[wpnuxt] Failed to fetch WordPress routes:', error)
}
}
after cursor) to fetch all content, or increase the first parameter.URI Normalization
WordPress URIs typically include trailing slashes (e.g., /hello-world/). This is important for SSG because the generated file path depends on the URI:
/hello-world/generateshello-world/index.html/hello-worldgenerateshello-world.html
Ensure consistency between your WordPress permalink structure and your Nuxt routing. The prerender:routes hook above uses URIs directly from WordPress, maintaining the correct format.
If you're using useNodeByUri() with route.path, note that Nuxt's route.path does not include a trailing slash by default. You may need to normalize:
const uri = route.path.endsWith('/') ? route.path : `${route.path}/`
const { data } = await useNodeByUri({ uri })
CI/CD Considerations
Schema Download
The GraphQL schema must be available at build time. In CI environments where WordPress may not be accessible:
const IS_CI = process.env.CI === 'true'
export default defineNuxtConfig({
wpNuxt: {
wordpressUrl: 'https://your-wordpress.com',
downloadSchema: !IS_CI, // Skip in CI, commit schema.graphql instead
},
})
If you skip schema download in CI, commit schema.graphql to your repository.
Route Fetching
The prerender:routes hook fetches routes from WordPress at build time. Ensure WordPress is accessible from your CI environment:
const IS_CI = process.env.CI === 'true'
const IS_VERCEL = !!process.env.VERCEL
export default defineNuxtConfig({
hooks: {
async 'prerender:routes'(ctx) {
// Fetch on Vercel (has internet) or locally, skip in restricted CI
if (IS_VERCEL || !IS_CI) {
await fetchWordPressRoutes(ctx.routes)
}
},
},
})
Environment Variables
Set WPNUXT_WORDPRESS_URL in your CI/CD environment:
# GitHub Actions example
env:
WPNUXT_WORDPRESS_URL: https://your-wordpress.com
Deploying
Vercel
Vercel automatically detects static Nuxt sites. See the Vercel deployment guide for details.
{
"buildCommand": "pnpm run build",
"outputDirectory": ".output/public"
}
Netlify
[build]
command = "pnpm run build"
publish = ".output/public"
Cloudflare Pages
[site]
bucket = ".output/public"
Or configure in the Cloudflare dashboard:
- Build command:
pnpm run build - Build output directory:
.output/public
Any Static Host
Run pnpm run build and upload the .output/public/ directory to any static hosting provider (AWS S3, GitHub Pages, Firebase Hosting, etc.).
Triggering Rebuilds
Since static sites don't update automatically, trigger a rebuild when content changes in WordPress:
- WordPress webhook — Use a plugin like WP Webhooks to send a POST request to your hosting provider's deploy hook when content is published
- Scheduled builds — Configure your CI to rebuild on a schedule (e.g., every hour)
- Manual — Trigger a rebuild from your hosting provider's dashboard
Most hosting providers (Vercel, Netlify, Cloudflare) support deploy hooks — a URL that triggers a new build when called.