mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
Building on top of @agadzik work (see first commit) Adds prerender route metadata to help downstream consumers understand the nature of build-time prerenders We break down prerender taxonomy as follows routeType describes what the prerender serves: * `route`: a non-UI endpoint, such as a Route Handler. * `page`: a page whose route parameters are fully known. * `shell`: a reusable PPR shell for a parameterized page. * `fallback`: a fallback that may serve or specialize additional parameter values. response describes how complete the static response is: * `complete`: the prerender contains the completed response. * `initial`: an initial response is available, but the page UI is incomplete; in practice this applies to partially prerenderable UI routes. * `empty`: there is no initial static response to send. compute describes the request-time work required: * `static`: no request-time server compute is needed. * `resuming`: request-time compute resumes from the static response. * `blocking`: the initial response is blocked until request-time compute starts, but it may stream while that compute continues. `htmlSize` reports the byte size of prerendered HTML when the output has HTML. The taxonomy is included only on a prerender group’s canonical primary output—the HTML output for pages or the response output for Route Handlers—and is omitted from secondary RSC and segment-data outputs. --------- Co-authored-by: agadzik <[email protected]>
152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import type { NextAdapter } from 'next'
|
|
|
|
type AdapterBuildContext = Parameters<NextAdapter['onBuildComplete']>[0]
|
|
|
|
type Classification = {
|
|
routeType: 'route' | 'page' | 'fallback'
|
|
response: 'complete' | 'initial' | 'empty'
|
|
compute: 'static' | 'blocking'
|
|
}
|
|
|
|
const classificationKey = ({ routeType, response, compute }: Classification) =>
|
|
`${routeType}/${response}/${compute}`
|
|
|
|
// Cache Components CI enables the feature for every fixture through this
|
|
// environment variable, which would change the expected non-CC taxonomy.
|
|
// Skip the whole suite so nextTestSetup does not start a build in that job.
|
|
;(process.env.__NEXT_CACHE_COMPONENTS === 'true' ? describe.skip : describe)(
|
|
'adapter-prerender-metadata-non-cc',
|
|
() => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
async function getPrerenders() {
|
|
const { outputs }: AdapterBuildContext = await next.readJSON(
|
|
'build-complete.json'
|
|
)
|
|
return outputs.prerenders
|
|
}
|
|
|
|
it('exercises every valid non-Cache-Components combination', async () => {
|
|
const cases: Array<
|
|
Classification & {
|
|
pathname: string
|
|
manifestSection: 'routes' | 'dynamicRoutes'
|
|
}
|
|
> = [
|
|
{
|
|
pathname: '/static-route',
|
|
manifestSection: 'routes',
|
|
routeType: 'route',
|
|
response: 'complete',
|
|
compute: 'static',
|
|
},
|
|
{
|
|
pathname: '/',
|
|
manifestSection: 'routes',
|
|
routeType: 'page',
|
|
response: 'complete',
|
|
compute: 'static',
|
|
},
|
|
{
|
|
pathname: '/gsp',
|
|
manifestSection: 'routes',
|
|
routeType: 'page',
|
|
response: 'complete',
|
|
compute: 'static',
|
|
},
|
|
{
|
|
pathname: '/products/[slug]',
|
|
manifestSection: 'dynamicRoutes',
|
|
routeType: 'page',
|
|
response: 'empty',
|
|
compute: 'blocking',
|
|
},
|
|
{
|
|
pathname: '/blocking/[id]',
|
|
manifestSection: 'dynamicRoutes',
|
|
routeType: 'page',
|
|
response: 'empty',
|
|
compute: 'blocking',
|
|
},
|
|
{
|
|
pathname: '/static-fallback/[id]',
|
|
manifestSection: 'dynamicRoutes',
|
|
routeType: 'fallback',
|
|
response: 'initial',
|
|
compute: 'static',
|
|
},
|
|
]
|
|
|
|
const expectedKeys = [
|
|
'route/complete/static',
|
|
'page/complete/static',
|
|
'page/empty/blocking',
|
|
'fallback/initial/static',
|
|
]
|
|
expect(Array.from(new Set(cases.map(classificationKey))).sort()).toEqual(
|
|
expectedKeys.sort()
|
|
)
|
|
|
|
const prerenders = await getPrerenders()
|
|
const manifest = await next.readJSON('.next/prerender-manifest.json')
|
|
for (const { pathname, manifestSection, ...classification } of cases) {
|
|
const output = prerenders.find(
|
|
(prerender) => prerender.pathname === pathname
|
|
)
|
|
expect(output).toMatchObject(classification)
|
|
expect(manifest[manifestSection][pathname]).toMatchObject(
|
|
classification
|
|
)
|
|
}
|
|
|
|
const omittedFallback = prerenders.find(
|
|
(prerender) => prerender.pathname === '/omitted/[id]'
|
|
)
|
|
expect(omittedFallback).toBeDefined()
|
|
expect(omittedFallback).not.toHaveProperty('routeType')
|
|
expect(omittedFallback).not.toHaveProperty('response')
|
|
expect(omittedFallback).not.toHaveProperty('compute')
|
|
})
|
|
|
|
it('exposes classification only on canonical responses', async () => {
|
|
const prerenders = await getPrerenders()
|
|
const completePage = prerenders.find((output) => output.pathname === '/')
|
|
const blockingPage = prerenders.find(
|
|
(output) => output.pathname === '/products/[slug]'
|
|
)
|
|
const route = prerenders.find(
|
|
(output) => output.pathname === '/static-route'
|
|
)
|
|
const rsc = prerenders.find((output) => output.pathname === '/index.rsc')
|
|
|
|
expect(completePage).toMatchObject({
|
|
routeType: 'page',
|
|
response: 'complete',
|
|
compute: 'static',
|
|
htmlSize: expect.any(Number),
|
|
})
|
|
expect(blockingPage).toMatchObject({
|
|
routeType: 'page',
|
|
response: 'empty',
|
|
compute: 'blocking',
|
|
})
|
|
expect(blockingPage).not.toHaveProperty('htmlSize')
|
|
expect(route).toMatchObject({
|
|
routeType: 'route',
|
|
response: 'complete',
|
|
compute: 'static',
|
|
})
|
|
expect(route).not.toHaveProperty('htmlSize')
|
|
|
|
expect(rsc).toBeDefined()
|
|
expect(rsc).not.toHaveProperty('routeType')
|
|
expect(rsc).not.toHaveProperty('response')
|
|
expect(rsc).not.toHaveProperty('compute')
|
|
expect(rsc).not.toHaveProperty('htmlSize')
|
|
})
|
|
}
|
|
)
|