From 6f719c3a594502e17d805f1c05a2309359d322b0 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sun, 8 Feb 2026 00:34:01 +0000 Subject: [PATCH 1/5] fix: provide payload fallback json --- modules/isr-fallback.ts | 10 ++++------ nuxt.config.ts | 22 +++++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/isr-fallback.ts b/modules/isr-fallback.ts index 13a7f92fe..097bfe467 100644 --- a/modules/isr-fallback.ts +++ b/modules/isr-fallback.ts @@ -13,6 +13,8 @@ export default defineNuxtModule({ } nuxt.hook('nitro:init', nitro => { + const htmlFallback = 'spa.prerender-fallback.html' + const jsonFallback = 'payload-fallback.json' nitro.hooks.hook('compiled', () => { const spaTemplate = readFileSync(nitro.options.output.publicDir + '/200.html', 'utf-8') for (const path of [ @@ -26,14 +28,10 @@ export default defineNuxtModule({ 'package/[org]/[name]/v/[version]', '', ]) { - const outputPath = resolve( - nitro.options.output.serverDir, - '..', - path, - 'spa.prerender-fallback.html', - ) + const outputPath = resolve(nitro.options.output.serverDir, '..', path, htmlFallback) mkdirSync(resolve(nitro.options.output.serverDir, '..', path), { recursive: true }) writeFileSync(outputPath, spaTemplate) + writeFileSync(outputPath.replace(htmlFallback, jsonFallback), '{}') } }) }) diff --git a/nuxt.config.ts b/nuxt.config.ts index e25a9b79f..687000feb 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -95,10 +95,14 @@ export default defineNuxtConfig({ }, }, // pages - '/package/:name': { isr: getISRConfig(60, true) }, - '/package/:name/v/:version': { isr: getISRConfig(60, true) }, - '/package/:org/:name': { isr: getISRConfig(60, true) }, - '/package/:org/:name/v/:version': { isr: getISRConfig(60, true) }, + '/package/:name': { isr: getISRConfig(60, { fallback: 'html' }) }, + '/package/:name/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, + '/package/:name/v/:version': { isr: getISRConfig(60, { fallback: 'html' }) }, + '/package/:name/v/:version/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, + '/package/:org/:name': { isr: getISRConfig(60, { fallback: 'html' }) }, + '/package/:org/:name/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, + '/package/:org/:name/v/:version': { isr: getISRConfig(60, { fallback: 'html' }) }, + '/package/:org/:name/v/:version/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, // infinite cache (versioned - doesn't change) '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, '/package-docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, @@ -281,11 +285,15 @@ export default defineNuxtConfig({ }, }) -function getISRConfig(expirationSeconds: number, fallback = false) { - if (fallback) { +interface ISRConfigOptions { + fallback?: 'html' | 'json' +} +function getISRConfig(expirationSeconds: number, options: ISRConfigOptions = {}) { + if (options.fallback) { return { expiration: expirationSeconds, - fallback: 'spa.prerender-fallback.html', + fallback: + options.fallback === 'html' ? 'spa.prerender-fallback.html' : 'payload-fallback.json', } as { expiration: number } } return { From 92abf691fae81731f0e66c823fd758830bf260cb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sun, 8 Feb 2026 00:44:09 +0000 Subject: [PATCH 2/5] chore: set json content-type headers --- nuxt.config.ts | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 687000feb..5d4cc102a 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -80,7 +80,7 @@ export default defineNuxtConfig({ '/api/registry/files/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, '/:pkg/.well-known/skills/**': { isr: 3600 }, '/:scope/:pkg/.well-known/skills/**': { isr: 3600 }, - '/__og-image__/**': { isr: getISRConfig(60) }, + '/__og-image__/**': getISRConfig(60), '/_avatar/**': { isr: 3600, proxy: 'https://www.gravatar.com/avatar/**' }, '/opensearch.xml': { isr: true }, '/oauth-client-metadata.json': { prerender: true }, @@ -95,14 +95,14 @@ export default defineNuxtConfig({ }, }, // pages - '/package/:name': { isr: getISRConfig(60, { fallback: 'html' }) }, - '/package/:name/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, - '/package/:name/v/:version': { isr: getISRConfig(60, { fallback: 'html' }) }, - '/package/:name/v/:version/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, - '/package/:org/:name': { isr: getISRConfig(60, { fallback: 'html' }) }, - '/package/:org/:name/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, - '/package/:org/:name/v/:version': { isr: getISRConfig(60, { fallback: 'html' }) }, - '/package/:org/:name/v/:version/_payload.json': { isr: getISRConfig(60, { fallback: 'json' }) }, + '/package/:name': getISRConfig(60, { fallback: 'html' }), + '/package/:name/_payload.json': getISRConfig(60, { fallback: 'json' }), + '/package/:name/v/:version': getISRConfig(60, { fallback: 'html' }), + '/package/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }), + '/package/:org/:name': getISRConfig(60, { fallback: 'html' }), + '/package/:org/:name/_payload.json': getISRConfig(60, { fallback: 'json' }), + '/package/:org/:name/v/:version': getISRConfig(60, { fallback: 'html' }), + '/package/:org/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }), // infinite cache (versioned - doesn't change) '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, '/package-docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } }, @@ -291,12 +291,23 @@ interface ISRConfigOptions { function getISRConfig(expirationSeconds: number, options: ISRConfigOptions = {}) { if (options.fallback) { return { - expiration: expirationSeconds, - fallback: - options.fallback === 'html' ? 'spa.prerender-fallback.html' : 'payload-fallback.json', - } as { expiration: number } + ...(options.fallback === 'json' + ? { + headers: { + 'content-type': 'application/json', + }, + } + : {}), + isr: { + expiration: expirationSeconds, + fallback: + options.fallback === 'html' ? 'spa.prerender-fallback.html' : 'payload-fallback.json', + } as { expiration: number }, + } } return { - expiration: expirationSeconds, + isr: { + expiration: expirationSeconds, + }, } } From 823449267f1d47d14f1d9fa10f64ce54a16cf5e0 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Feb 2026 01:01:45 +0000 Subject: [PATCH 3/5] fix: ensure payload fallbacks have json headers --- vercel.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vercel.json b/vercel.json index 602fb126a..003c27c17 100644 --- a/vercel.json +++ b/vercel.json @@ -82,5 +82,16 @@ ], "destination": "https://github.com/npmx-dev/npmx.dev/blob/main/LICENSE" } + ], + "headers": [ + { + "source": "**/*_payload.json", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } ] } From 488b305822b106ebac5fe17e5be9b8b9192226f5 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Feb 2026 01:02:34 +0000 Subject: [PATCH 4/5] fix: try different pattern --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 003c27c17..4bc676d27 100644 --- a/vercel.json +++ b/vercel.json @@ -85,7 +85,7 @@ ], "headers": [ { - "source": "**/*_payload.json", + "source": "/:path*_payload.json", "headers": [ { "key": "Content-Type", From 0edfeb417828ebb5af76ff75588cccfeae101596 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Feb 2026 01:06:43 +0000 Subject: [PATCH 5/5] chore: pass `initialHeaders` --- nuxt.config.ts | 8 +------- vercel.json | 11 ----------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 9916ceeed..39d4bbb4f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -324,17 +324,11 @@ interface ISRConfigOptions { function getISRConfig(expirationSeconds: number, options: ISRConfigOptions = {}) { if (options.fallback) { return { - ...(options.fallback === 'json' - ? { - headers: { - 'content-type': 'application/json', - }, - } - : {}), isr: { expiration: expirationSeconds, fallback: options.fallback === 'html' ? 'spa.prerender-fallback.html' : 'payload-fallback.json', + initialHeaders: options.fallback === 'json' ? { 'content-type': 'application/json' } : {}, } as { expiration: number }, } } diff --git a/vercel.json b/vercel.json index 4bc676d27..602fb126a 100644 --- a/vercel.json +++ b/vercel.json @@ -82,16 +82,5 @@ ], "destination": "https://github.com/npmx-dev/npmx.dev/blob/main/LICENSE" } - ], - "headers": [ - { - "source": "/:path*_payload.json", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } ] }