Skip to content

fix: create cache entries for fallback pages to support next@canary #2649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

58 changes: 52 additions & 6 deletions src/build/content/prerendered.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { existsSync } from 'node:fs'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { join as posixJoin } from 'node:path/posix'

import { trace } from '@opentelemetry/api'
import { wrapTracer } from '@opentelemetry/api/experimental'
Expand Down Expand Up @@ -41,17 +42,28 @@ const writeCacheEntry = async (
}

/**
* Normalize routes by stripping leading slashes and ensuring root path is index
* Normalize routes by ensuring leading slashes and ensuring root path is index
*/
const routeToFilePath = (path: string) => (path === '/' ? '/index' : path)
const routeToFilePath = (path: string) => {
if (path === '/') {
return '/index'
}

if (path.startsWith('/')) {
return path
}

return `/${path}`
}

const buildPagesCacheValue = async (
path: string,
shouldUseEnumKind: boolean,
shouldSkipJson = false,
): Promise<NetlifyCachedPageValue> => ({
kind: shouldUseEnumKind ? 'PAGES' : 'PAGE',
html: await readFile(`${path}.html`, 'utf-8'),
pageData: JSON.parse(await readFile(`${path}.json`, 'utf-8')),
pageData: shouldSkipJson ? {} : JSON.parse(await readFile(`${path}.json`, 'utf-8')),
headers: undefined,
status: undefined,
})
Expand Down Expand Up @@ -146,8 +158,8 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
})
: false

await Promise.all(
Object.entries(manifest.routes).map(
await Promise.all([
...Object.entries(manifest.routes).map(
([route, meta]): Promise<void> =>
limitConcurrentPrerenderContentHandling(async () => {
const lastModified = meta.initialRevalidateSeconds
Expand Down Expand Up @@ -195,7 +207,41 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
await writeCacheEntry(key, value, lastModified, ctx)
}),
),
)
...Object.entries(manifest.dynamicRoutes).map(async ([route, meta]) => {
// fallback can be `string | false | null`
// - `string` - when user use pages router with `fallback: true`, and then it's html file path
// - `null` - when user use pages router with `fallback: 'block'` or app router with `export const dynamicParams = true`
// - `false` - when user use pages router with `fallback: false` or app router with `export const dynamicParams = false`
if (typeof meta.fallback === 'string') {
// https://github.com/vercel/next.js/pull/68603 started using route cache to serve fallbacks
// so we have to seed blobs with fallback entries

// create cache entry for pages router with `fallback: true` case
await limitConcurrentPrerenderContentHandling(async () => {
// dynamic routes don't have entries for each locale so we have to generate them
// ourselves. If i18n is not used we use empty string as "locale" to be able to use
// same handling wether i18n is used or not
const locales = ctx.buildConfig.i18n?.locales ?? ['']

const lastModified = Date.now()
for (const locale of locales) {
const key = routeToFilePath(posixJoin(locale, route))
const value = await buildPagesCacheValue(
join(ctx.publishDir, 'server/pages', key),
shouldUseEnumKind,
true, // there is no corresponding json file for fallback, so we are skipping it for this entry
)
// Netlify Forms are not support and require a workaround
if (value.kind === 'PAGE' || value.kind === 'PAGES' || value.kind === 'APP_PAGE') {
verifyNetlifyForms(ctx, value.html)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a blocker, but i'd argue that the Netlify Forms verification here is not adding much in terms of user value and we can strip it out to reduce noise (also APP_PAGE can never be true)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll drop that completely form here - we actually already are doing this right now because of https://github.com/netlify/next-runtime/blob/5f5ec0651726abf1027319707beaa1580a0c5dd8/src/build/content/static.ts#L35 so this was just doing same check again on same content

This was just copied from above code heh


await writeCacheEntry(key, value, lastModified, ctx)
}
})
}
}),
])

// app router 404 pages are not in the prerender manifest
// so we need to check for them manually
Expand Down