Skip to content

Commit ca9ec06

Browse files
authored
fix: expose configured sources during prerender (#647)
1 parent c568550 commit ca9ec06

7 files changed

Lines changed: 51 additions & 22 deletions

File tree

src/module.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,23 +1064,17 @@ export async function readSourcesFromFilesystem() {
10641064
`
10651065
}
10661066

1067-
// Skip virtual templates when prerendering - sources are written to filesystem instead
1068-
// In dev mode, always generate sources even if prerenderSitemap is true (e.g. zeroRuntime)
1069-
if (prerenderSitemap && !nuxt.options.dev) {
1070-
nitroConfig.virtual['#sitemap-virtual/global-sources.mjs'] = `export const sources = []`
1071-
nitroConfig.virtual[`#sitemap-virtual/child-sources.mjs`] = `export const sources = {}`
1067+
// Virtual templates provide the initial build-time sources. During
1068+
// prerender, the filesystem handoff replaces them with the final sources
1069+
// after all routes have been crawled.
1070+
nitroConfig.virtual['#sitemap-virtual/global-sources.mjs'] = async () => {
1071+
const globalSources = await generateGlobalSources()
1072+
return `export const sources = ${JSON.stringify(globalSources, null, 4)}`
10721073
}
1073-
else {
1074-
// Virtual templates generate sources data - will be cached in storage on first use
1075-
nitroConfig.virtual['#sitemap-virtual/global-sources.mjs'] = async () => {
1076-
const globalSources = await generateGlobalSources()
1077-
return `export const sources = ${JSON.stringify(globalSources, null, 4)}`
1078-
}
10791074

1080-
nitroConfig.virtual![`#sitemap-virtual/child-sources.mjs`] = async () => {
1081-
const childSources = await generateChildSources()
1082-
return `export const sources = ${JSON.stringify(childSources, null, 4)}`
1083-
}
1075+
nitroConfig.virtual![`#sitemap-virtual/child-sources.mjs`] = async () => {
1076+
const childSources = await generateChildSources()
1077+
return `export const sources = ${JSON.stringify(childSources, null, 4)}`
10841078
}
10851079
})
10861080

src/runtime/server/sitemap/builder/sitemap-index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ async function buildSitemapIndexInternal(resolvers: NitroUrlResolvers, runtimeCo
133133

134134
export async function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp) {
135135
// Check if should use cached version.
136-
// Skip caching during prerender: sources are written to disk by `prerender:done`, so
137-
// an early crawl would otherwise poison the cache with an empty result.
136+
// Skip caching during prerender so the final filesystem source handoff is visible.
138137
if (!import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === 'number' && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {
139138
return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro)
140139
}

src/runtime/server/sitemap/builder/sitemap.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,7 @@ export const buildResolvedSitemapUrlsCached = defineCachedFunction(
359359
// Routes between Nitro's storage-backed cache (production) and direct execution. Chunks of the
360360
// same base sitemap share one cache entry so the source fetch + normalize + sort runs once per
361361
// `cacheMaxAgeSeconds` window. Edge-runtime safe: relies on Nitro's storage layer, no module
362-
// state. Dev and prerender skip the cache (prerender to avoid poisoning from early empty-source
363-
// reads; dev to keep iteration fast).
362+
// state. Dev and prerender skip the cache so updated sources remain visible.
364363
export async function getResolvedSitemapUrls(
365364
effectiveSitemap: SitemapDefinition,
366365
matchName: string,

src/runtime/server/sitemap/nitro.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,8 @@ export async function createSitemap(event: H3Event, definition: SitemapDefinitio
314314
const shouldStream = !!runtimeConfig.experimentalStreaming && !import.meta.prerender
315315

316316
// Choose between cached or direct generation.
317-
// Skip caching during prerender: the crawl may run before `prerender:done` has written
318-
// `global-sources.json`, so an early empty result would poison the cache and be returned
319-
// on the follow-up render, shipping an empty sitemap.
317+
// Skip caching during prerender so the final filesystem source handoff can replace
318+
// the initial build-time sources after the crawl.
320319
// A serialized XML cache necessarily buffers the entire response. Streaming mode caches
321320
// the finalized render plan instead and serializes those resolved URLs on demand.
322321
const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === 'number' && runtimeConfig.cacheMaxAgeSeconds > 0
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { readFile } from 'node:fs/promises'
2+
import { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'
3+
import { describe, expect, it } from 'vitest'
4+
5+
describe('prerender source handoff', () => {
6+
it('makes configured URLs available before prerender:done', async () => {
7+
const { resolve } = createResolver(import.meta.url)
8+
const rootDir = resolve('../../fixtures/prerender-early-source')
9+
const nuxt = await loadNuxt({ rootDir })
10+
11+
await buildNuxt(nuxt)
12+
13+
const earlySitemap = await readFile(resolve(rootDir, '.output/public/early-sitemap/index.html'), 'utf8')
14+
expect(earlySitemap).toContain('<loc>https://example.com/configured</loc>')
15+
}, 120000)
16+
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import NuxtSitemap from '../../../src/module'
2+
3+
export default defineNuxtConfig({
4+
modules: [NuxtSitemap],
5+
site: {
6+
url: 'https://example.com',
7+
},
8+
nitro: {
9+
prerender: {
10+
routes: ['/early-sitemap'],
11+
},
12+
},
13+
sitemap: {
14+
credits: false,
15+
excludeAppSources: true,
16+
urls: ['/configured'],
17+
zeroRuntime: true,
18+
},
19+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineEventHandler } from 'h3'
2+
3+
export default defineEventHandler(event => event.$fetch('/sitemap.xml'))

0 commit comments

Comments
 (0)