rsnext/test/e2e/app-dir/app-static/app-static.test.ts

2327 lines
84 KiB
TypeScript
Raw Normal View History

import globOrig from 'glob'
import cheerio from 'cheerio'
import { promisify } from 'util'
import { join } from 'path'
import { createNextDescribe } from 'e2e-utils'
import { check, fetchViaHTTP, normalizeRegEx, waitFor } from 'next-test-utils'
import stripAnsi from 'strip-ansi'
const glob = promisify(globOrig)
createNextDescribe(
'app-dir static/dynamic handling',
{
files: __dirname,
env: {
NEXT_DEBUG_BUILD: '1',
...(process.env.CUSTOM_CACHE_HANDLER
? {
CUSTOM_CACHE_HANDLER: process.env.CUSTOM_CACHE_HANDLER,
}
: {}),
},
},
({ next, isNextDev: isDev, isNextStart, isNextDeploy }) => {
let prerenderManifest
let buildCliOutputIndex = 0
beforeAll(async () => {
if (isNextStart) {
prerenderManifest = JSON.parse(
await next.readFile('.next/prerender-manifest.json')
)
buildCliOutputIndex = next.cliOutput.length
}
})
if (isNextStart) {
it('should propagate unstable_cache tags correctly', async () => {
const meta = JSON.parse(
await next.readFile(
'.next/server/app/variable-revalidate/revalidate-360-isr.meta'
)
)
expect(meta.headers['x-next-cache-tags']).toContain(
'unstable_cache_tag1'
)
})
}
it('should correctly include headers instance in cache key', async () => {
const res = await next.fetch('/variable-revalidate/headers-instance')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const data1 = $('#page-data').text()
const data2 = $('#page-data2').text()
expect(data1).not.toBe(data2)
expect(data1).toBeTruthy()
expect(data2).toBeTruthy()
})
Optimize Next.js bootup compilation (#50379) ## What? Currently we use the initial compile to add entrypoints that we know are going to be used in the application. For example the Next.js runtime, `react`, `react-dom`, and such. While this was the right default when Next.js only had `pages`, it's no longer true when both `pages` and `app` exist. E.g. when you're working on a page using App Router we'd still compile `pages/_app`, `pages/_document`, and `pages/_error` even though those would not be used. In case of larger applications (e.g. Vercel's application) this would mean thousands of extra modules being compiled even though you don't need them for the page you're looking at. Similarly we'd compile the Next.js runtime for App Router even when you're only using `pages`. This PR changes the handling to only compile the entries that are needed for the current set of visited pages (on-demand-entries). If that set only includes `app` entrypoints then the `pages` related files will be excluded. If the set contains both `app` and `pages` both will be included. Similarly for `amp`, if you don't use `amp: true` / `amp: hybrid` the development runtime will not be compiled. 📔 Note: This is specifically for webpack, Turbopack already compiles everything needed lazily so it didn't have this limitation. #### Before ``` - event compiled client and server successfully in 1079 ms (306 modules) - event compiled client and server successfully in 155 ms (306 modules) ``` With opening `/`: ``` - event compiled client and server successfully in 1118 ms (306 modules) - event compiled client and server successfully in 157 ms (306 modules) - event compiled client and server successfully in 599 ms (486 modules) ``` Total: 1.874ms (Note: This number is much higher when `pages/_app` imports many modules). #### After ``` - event compiled client and server successfully in 118 ms (20 modules) - event compiled client and server successfully in 65 ms (20 modules) ``` 📔 Note: opening the page then causes the Next.js / React runtime to be compiled ofcourse ``` - event compiled client and server successfully in 115 ms (20 modules) - event compiled client and server successfully in 57 ms (20 modules) - event compiled client and server successfully in 1137 ms (361 modules) ``` Total: 1.309ms (Note: This number is not affected by`pages/_app` importing many modules). ## How? We can only apply this optimization after we've looped over the list of on-demand entries, as that has the required metadata. Hence why I went with deleting the keys for each respective type of entrypoint (`pages`, `app`, `amp`). <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation or adding/fixing Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --------- Co-authored-by: JJ Kasper <jj@jjsweb.site>
2023-05-30 12:03:24 +02:00
it.skip.each([
{
path: '/react-fetch-deduping-node',
},
{
path: '/react-fetch-deduping-edge',
},
])(
'should correctly de-dupe fetch without next cache $path',
async ({ path }) => {
for (let i = 0; i < 5; i++) {
const res = await next.fetch(path, {
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const data1 = $('#data-1').text()
const data2 = $('#data-2').text()
expect(data1).toBeTruthy()
expect(data1).toBe(data2)
await waitFor(250)
}
}
)
it.each([
{ pathname: '/unstable-cache-node' },
{ pathname: '/unstable-cache-edge' },
{ pathname: '/api/unstable-cache-node' },
{ pathname: '/api/unstable-cache-edge' },
])('unstable-cache should work in pages$pathname', async ({ pathname }) => {
let res = await next.fetch(pathname)
expect(res.status).toBe(200)
const isApi = pathname.startsWith('/api')
let prevData
if (isApi) {
prevData = await res.json()
} else {
const initialHtml = await res.text()
const initial$ = isApi ? undefined : cheerio.load(initialHtml)
prevData = JSON.parse(initial$('#props').text())
}
expect(prevData.data.random).toBeTruthy()
await check(async () => {
res = await next.fetch(pathname)
expect(res.status).toBe(200)
let curData
if (isApi) {
curData = await res.json()
} else {
const curHtml = await res.text()
const cur$ = cheerio.load(curHtml)
curData = JSON.parse(cur$('#props').text())
}
try {
expect(curData.data.random).toBeTruthy()
expect(curData.data.random).toBe(prevData.data.random)
} finally {
prevData = curData
}
return 'success'
}, 'success')
})
it('should not have cache tags header for non-minimal mode', async () => {
for (const path of [
'/ssr-forced',
'/ssr-forced',
'/variable-revalidate/revalidate-3',
'/variable-revalidate/revalidate-360',
'/variable-revalidate/revalidate-360-isr',
]) {
const res = await fetchViaHTTP(next.url, path, undefined, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBeFalsy()
}
})
if (isDev) {
it('should error correctly for invalid params from generateStaticParams', async () => {
await next.patchFile(
'app/invalid/[slug]/page.js',
`
export function generateStaticParams() {
return [{slug: { invalid: true }}]
}
`
)
// The page may take a moment to compile, so try it a few times.
await check(async () => {
return next.render('/invalid/first')
}, /A required parameter \(slug\) was not provided as a string received object/)
await next.deleteFile('app/invalid/[slug]/page.js')
})
it('should correctly handle multi-level generateStaticParams when some levels are missing', async () => {
const browser = await next.browser('/flight/foo/bar')
const v = ~~(Math.random() * 1000)
await browser.eval(`document.cookie = "test-cookie=${v}"`)
await browser.elementByCss('button').click()
await check(async () => {
return await browser.elementByCss('h1').text()
}, v.toString())
})
}
it('should correctly skip caching POST fetch for POST handler', async () => {
const res = await next.fetch('/route-handler/post', {
method: 'POST',
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data).toBeTruthy()
for (let i = 0; i < 5; i++) {
const res2 = await next.fetch('/route-handler/post', {
method: 'POST',
})
expect(res2.status).toBe(200)
const newData = await res2.json()
expect(newData).toBeTruthy()
expect(newData).not.toEqual(data)
}
})
if (!process.env.CUSTOM_CACHE_HANDLER) {
it.each([
{
type: 'edge route handler',
revalidateApi: '/api/revalidate-tag-edge',
},
{
type: 'node route handler',
revalidateApi: '/api/revalidate-tag-node',
},
])(
'it should revalidate tag correctly with $type',
async ({ revalidateApi }) => {
const initRes = await next.fetch(
'/variable-revalidate/revalidate-360'
)
const html = await initRes.text()
const $ = cheerio.load(html)
const initLayoutData = $('#layout-data').text()
const initPageData = $('#page-data').text()
const routeHandlerRes = await next.fetch(
'/route-handler/revalidate-360'
)
const initRouteHandlerData = await routeHandlerRes.json()
const edgeRouteHandlerRes = await next.fetch(
'/route-handler-edge/revalidate-360'
)
const initEdgeRouteHandlerRes = await edgeRouteHandlerRes.json()
expect(initLayoutData).toBeTruthy()
expect(initPageData).toBeTruthy()
await check(async () => {
const revalidateRes = await next.fetch(
`${revalidateApi}?tag=thankyounext`
)
expect((await revalidateRes.json()).revalidated).toBe(true)
const newRes = await next.fetch(
'/variable-revalidate/revalidate-360'
)
const cacheHeader = newRes.headers.get('x-nextjs-cache')
if ((global as any).isNextStart && cacheHeader) {
expect(cacheHeader).toBe('MISS')
}
const newHtml = await newRes.text()
const new$ = cheerio.load(newHtml)
const newLayoutData = new$('#layout-data').text()
const newPageData = new$('#page-data').text()
const newRouteHandlerRes = await next.fetch(
'/route-handler/revalidate-360'
)
const newRouteHandlerData = await newRouteHandlerRes.json()
const newEdgeRouteHandlerRes = await next.fetch(
'/route-handler-edge/revalidate-360'
)
const newEdgeRouteHandlerData = await newEdgeRouteHandlerRes.json()
expect(newLayoutData).toBeTruthy()
expect(newPageData).toBeTruthy()
expect(newRouteHandlerData).toBeTruthy()
expect(newEdgeRouteHandlerData).toBeTruthy()
expect(newLayoutData).not.toBe(initLayoutData)
expect(newPageData).not.toBe(initPageData)
expect(newRouteHandlerData).not.toEqual(initRouteHandlerData)
expect(newEdgeRouteHandlerData).not.toEqual(initEdgeRouteHandlerRes)
return 'success'
}, 'success')
}
)
}
// On-Demand Revalidate has not effect in dev since app routes
// aren't considered static until prerendering
if (!(global as any).isNextDev && !process.env.CUSTOM_CACHE_HANDLER) {
it('should not revalidate / when revalidate is not used', async () => {
let prevData
for (let i = 0; i < 5; i++) {
const res = await next.fetch('/')
const html = await res.text()
const $ = cheerio.load(html)
const data = $('#page-data').text()
expect(res.status).toBe(200)
if (prevData) {
expect(prevData).toBe(data)
prevData = data
}
await waitFor(500)
}
if (isNextStart) {
expect(next.cliOutput.substring(buildCliOutputIndex)).not.toContain(
'rendering index'
)
}
})
it.each([
{
type: 'edge route handler',
revalidateApi: '/api/revalidate-path-edge',
},
{
type: 'node route handler',
revalidateApi: '/api/revalidate-path-node',
},
])(
'it should revalidate correctly with $type',
async ({ revalidateApi }) => {
const initRes = await next.fetch(
'/variable-revalidate/revalidate-360-isr'
)
const html = await initRes.text()
const $ = cheerio.load(html)
const initLayoutData = $('#layout-data').text()
const initPageData = $('#page-data').text()
expect(initLayoutData).toBeTruthy()
expect(initPageData).toBeTruthy()
await check(async () => {
const revalidateRes = await next.fetch(
`${revalidateApi}?path=/variable-revalidate/revalidate-360-isr`
)
expect((await revalidateRes.json()).revalidated).toBe(true)
const newRes = await next.fetch(
'/variable-revalidate/revalidate-360-isr'
)
const newHtml = await newRes.text()
const new$ = cheerio.load(newHtml)
const newLayoutData = new$('#layout-data').text()
const newPageData = new$('#page-data').text()
expect(newLayoutData).toBeTruthy()
expect(newPageData).toBeTruthy()
expect(newLayoutData).not.toBe(initLayoutData)
expect(newPageData).not.toBe(initPageData)
return 'success'
}, 'success')
}
)
}
// On-Demand Revalidate has not effect in dev
if (!(global as any).isNextDev && !process.env.CUSTOM_CACHE_HANDLER) {
it('should revalidate all fetches during on-demand revalidate', async () => {
const initRes = await next.fetch(
'/variable-revalidate/revalidate-360-isr'
)
const html = await initRes.text()
const $ = cheerio.load(html)
const initLayoutData = $('#layout-data').text()
const initPageData = $('#page-data').text()
expect(initLayoutData).toBeTruthy()
expect(initPageData).toBeTruthy()
await check(async () => {
const revalidateRes = await next.fetch(
'/api/revalidate-path-node?path=/variable-revalidate/revalidate-360-isr'
)
expect((await revalidateRes.json()).revalidated).toBe(true)
const newRes = await next.fetch(
'/variable-revalidate/revalidate-360-isr'
)
const newHtml = await newRes.text()
const new$ = cheerio.load(newHtml)
const newLayoutData = new$('#layout-data').text()
const newPageData = new$('#page-data').text()
expect(newLayoutData).toBeTruthy()
expect(newPageData).toBeTruthy()
expect(newLayoutData).not.toBe(initLayoutData)
expect(newPageData).not.toBe(initPageData)
return 'success'
}, 'success')
})
}
it('should correctly handle fetchCache = "force-no-store"', async () => {
const initRes = await next.fetch('/force-no-store')
const html = await initRes.text()
const $ = cheerio.load(html)
const initPageData = $('#page-data').text()
expect(initPageData).toBeTruthy()
const newRes = await next.fetch('/force-no-store')
const newHtml = await newRes.text()
const new$ = cheerio.load(newHtml)
const newPageData = new$('#page-data').text()
expect(newPageData).toBeTruthy()
expect(newPageData).not.toBe(initPageData)
})
if (!process.env.CUSTOM_CACHE_HANDLER) {
it('should revalidate correctly with config and fetch revalidate', async () => {
const initial$ = await next.render$(
'/variable-config-revalidate/revalidate-3'
)
const initialDate = initial$('#date').text()
const initialRandomData = initial$('#random-data').text()
expect(initialDate).toBeTruthy()
expect(initialRandomData).toBeTruthy()
let prevInitialDate
let prevInitialRandomData
// wait for a fresh revalidation
await check(async () => {
const $ = await next.render$(
'/variable-config-revalidate/revalidate-3'
)
prevInitialDate = $('#date').text()
prevInitialRandomData = $('#random-data').text()
expect(prevInitialDate).not.toBe(initialDate)
expect(prevInitialRandomData).not.toBe(initialRandomData)
return 'success'
}, 'success')
// the date should revalidate first after 3 seconds
// while the fetch data stays in place for 9 seconds
await check(async () => {
const $ = await next.render$(
'/variable-config-revalidate/revalidate-3'
)
const curDate = $('#date').text()
const curRandomData = $('#random-data').text()
expect(curDate).not.toBe(prevInitialDate)
expect(curRandomData).not.toBe(prevInitialRandomData)
prevInitialDate = curDate
prevInitialRandomData = curRandomData
return 'success'
}, 'success')
})
}
it('should not cache non-ok statusCode', async () => {
await check(async () => {
const $ = await next.render$('/variable-revalidate/status-code')
const origData = JSON.parse($('#page-data').text())
expect(origData.status).toBe(404)
const new$ = await next.render$('/variable-revalidate/status-code')
const newData = JSON.parse(new$('#page-data').text())
expect(newData.status).toBe(origData.status)
expect(newData.text).not.toBe(origData.text)
return 'success'
}, 'success')
})
if (isNextStart) {
it('should output HTML/RSC files for static paths', async () => {
const files = (
await glob('**/*', {
cwd: join(next.testDir, '.next/server/app'),
})
)
.filter((file) => file.match(/.*\.(js|html|rsc)$/))
.map((file) => {
return file.replace(
/partial-gen-params-no-additional-([\w]{1,})\/([\w]{1,})\/([\d]{1,})/,
'partial-gen-params-no-additional-$1/$2/RAND'
)
})
expect(files.sort()).toEqual(
[
'_not-found.html',
'_not-found.js',
'_not-found.rsc',
'_not-found_client-reference-manifest.js',
'page.js',
'index.rsc',
'index.html',
'blog/seb.rsc',
'blog/tim.rsc',
'blog/seb.html',
'blog/tim.html',
'blog/styfle.rsc',
'force-cache.rsc',
'blog/styfle.html',
'force-cache.html',
'ssg-draft-mode.rsc',
'ssr-forced/page.js',
'custom.prefetch.rsc',
'force-cache/page.js',
'ssg-draft-mode.html',
'(new)/custom/page.js',
'force-static/page.js',
'response-url/page.js',
'blog/[author]/page.js',
'default-cache/page.js',
'fetch-no-cache/page.js',
'force-no-store/page.js',
'force-static/first.rsc',
'api/draft-mode/route.js',
'blog/tim/first-post.rsc',
'force-static/first.html',
'force-static/second.rsc',
'ssg-draft-mode/test.rsc',
'ssr-forced.prefetch.rsc',
'blog/seb/second-post.rsc',
'blog/tim/first-post.html',
'force-static/second.html',
'ssg-draft-mode/test.html',
'blog/seb/second-post.html',
'force-static.prefetch.rsc',
'ssg-draft-mode/test-2.rsc',
'blog/styfle/first-post.rsc',
'default-cache.prefetch.rsc',
'dynamic-error/[id]/page.js',
'response-url.prefetch.rsc',
'ssg-draft-mode/test-2.html',
'blog/styfle/first-post.html',
'blog/styfle/second-post.rsc',
'fetch-no-cache.prefetch.rsc',
'force-no-store.prefetch.rsc',
'force-static/[slug]/page.js',
'hooks/use-pathname/slug.rsc',
'hooks/use-search-params.rsc',
'route-handler/post/route.js',
'blog/[author]/[slug]/page.js',
'blog/styfle/second-post.html',
'hooks/use-pathname/slug.html',
'hooks/use-search-params.html',
'flight/[slug]/[slug2]/page.js',
'variable-revalidate/cookie.rsc',
'hooks/use-search-params/page.js',
'ssr-auto/cache-no-store/page.js',
'variable-revalidate/cookie.html',
'api/revalidate-tag-edge/route.js',
'api/revalidate-tag-node/route.js',
'variable-revalidate/encoding.rsc',
'api/revalidate-path-edge/route.js',
'api/revalidate-path-node/route.js',
'gen-params-dynamic/[slug]/page.js',
'hooks/use-pathname/[slug]/page.js',
'page_client-reference-manifest.js',
'react-fetch-deduping-edge/page.js',
'react-fetch-deduping-node/page.js',
'variable-revalidate/encoding.html',
'variable-revalidate/cookie/page.js',
'gen-params-dynamic/one.prefetch.rsc',
'ssg-draft-mode/[[...route]]/page.js',
'variable-revalidate/post-method.rsc',
'dynamic-no-gen-params/[slug]/page.js',
'ssr-auto/cache-no-store.prefetch.rsc',
'static-to-dynamic-error/[id]/page.js',
'variable-revalidate/encoding/page.js',
'variable-revalidate/no-store/page.js',
'variable-revalidate/post-method.html',
'variable-revalidate/revalidate-3.rsc',
'gen-params-dynamic-revalidate/one.rsc',
'route-handler/revalidate-360/route.js',
'route-handler/static-cookies/route.js',
'variable-revalidate-edge/body/page.js',
'variable-revalidate/authorization.rsc',
'variable-revalidate/revalidate-3.html',
'force-dynamic-prerender/[slug]/page.js',
'gen-params-dynamic-revalidate/one.html',
'react-fetch-deduping-node.prefetch.rsc',
'ssr-auto/fetch-revalidate-zero/page.js',
'variable-revalidate/authorization.html',
'force-dynamic-no-prerender/[id]/page.js',
'variable-revalidate/post-method/page.js',
'variable-revalidate/status-code/page.js',
'dynamic-no-gen-params-ssr/[slug]/page.js',
'hooks/use-search-params/force-static.rsc',
'partial-gen-params/[lang]/[slug]/page.js',
'variable-revalidate/headers-instance.rsc',
'variable-revalidate/revalidate-3/page.js',
'force-dynamic-catch-all/slug.prefetch.rsc',
'hooks/use-search-params/force-static.html',
'hooks/use-search-params/with-suspense.rsc',
'route-handler/revalidate-360-isr/route.js',
'variable-revalidate-edge/encoding/page.js',
'variable-revalidate-edge/no-store/page.js',
'variable-revalidate/authorization/page.js',
'variable-revalidate/headers-instance.html',
'hooks/use-search-params/with-suspense.html',
'route-handler-edge/revalidate-360/route.js',
'variable-revalidate/no-store.prefetch.rsc',
'variable-revalidate/revalidate-360-isr.rsc',
'variable-revalidate/revalidate-360/page.js',
'ssr-auto/fetch-revalidate-zero.prefetch.rsc',
'static-to-dynamic-error-forced/[id]/page.js',
'variable-config-revalidate/revalidate-3.rsc',
'variable-revalidate/revalidate-360-isr.html',
'gen-params-dynamic-revalidate/[slug]/page.js',
'hooks/use-search-params/force-static/page.js',
'ssr-forced/page_client-reference-manifest.js',
'variable-config-revalidate/revalidate-3.html',
'variable-revalidate-edge/post-method/page.js',
'variable-revalidate/headers-instance/page.js',
'variable-revalidate/status-code.prefetch.rsc',
'force-cache/page_client-reference-manifest.js',
'hooks/use-search-params/with-suspense/page.js',
'variable-revalidate-edge/revalidate-3/page.js',
'(new)/custom/page_client-reference-manifest.js',
'force-static/page_client-reference-manifest.js',
'response-url/page_client-reference-manifest.js',
'variable-revalidate/revalidate-360-isr/page.js',
'blog/[author]/page_client-reference-manifest.js',
'default-cache/page_client-reference-manifest.js',
'force-dynamic-prerender/frameworks.prefetch.rsc',
'variable-config-revalidate/revalidate-3/page.js',
'variable-revalidate/post-method-request/page.js',
'variable-revalidate/revalidate-360.prefetch.rsc',
'fetch-no-cache/page_client-reference-manifest.js',
'force-dynamic-catch-all/[slug]/[[...id]]/page.js',
'force-no-store/page_client-reference-manifest.js',
'partial-gen-params-no-additional-lang/en/RAND.rsc',
'partial-gen-params-no-additional-lang/fr/RAND.rsc',
'partial-gen-params-no-additional-slug/en/RAND.rsc',
'partial-gen-params-no-additional-slug/fr/RAND.rsc',
'partial-gen-params-no-additional-lang/en/RAND.html',
'partial-gen-params-no-additional-lang/en/first.rsc',
'partial-gen-params-no-additional-lang/fr/RAND.html',
'partial-gen-params-no-additional-lang/fr/first.rsc',
'partial-gen-params-no-additional-slug/en/RAND.html',
'partial-gen-params-no-additional-slug/en/first.rsc',
'partial-gen-params-no-additional-slug/fr/RAND.html',
'partial-gen-params-no-additional-slug/fr/first.rsc',
'partial-gen-params-no-additional-lang/en/first.html',
'partial-gen-params-no-additional-lang/en/second.rsc',
'partial-gen-params-no-additional-lang/fr/first.html',
'partial-gen-params-no-additional-lang/fr/second.rsc',
'partial-gen-params-no-additional-slug/en/first.html',
'partial-gen-params-no-additional-slug/en/second.rsc',
'partial-gen-params-no-additional-slug/fr/first.html',
'partial-gen-params-no-additional-slug/fr/second.rsc',
'dynamic-error/[id]/page_client-reference-manifest.js',
'partial-gen-params-no-additional-lang/en/second.html',
'partial-gen-params-no-additional-lang/fr/second.html',
'partial-gen-params-no-additional-slug/en/second.html',
'partial-gen-params-no-additional-slug/fr/second.html',
'variable-revalidate/post-method-request.prefetch.rsc',
'variable-revalidate-edge/post-method-request/page.js',
'force-static/[slug]/page_client-reference-manifest.js',
'blog/[author]/[slug]/page_client-reference-manifest.js',
'flight/[slug]/[slug2]/page_client-reference-manifest.js',
'hooks/use-search-params/page_client-reference-manifest.js',
'ssr-auto/cache-no-store/page_client-reference-manifest.js',
'gen-params-dynamic/[slug]/page_client-reference-manifest.js',
'hooks/use-pathname/[slug]/page_client-reference-manifest.js',
'partial-gen-params-no-additional-lang/[lang]/[slug]/page.js',
'partial-gen-params-no-additional-slug/[lang]/[slug]/page.js',
'react-fetch-deduping-edge/page_client-reference-manifest.js',
'react-fetch-deduping-node/page_client-reference-manifest.js',
'variable-revalidate/cookie/page_client-reference-manifest.js',
'ssg-draft-mode/[[...route]]/page_client-reference-manifest.js',
'dynamic-no-gen-params/[slug]/page_client-reference-manifest.js',
'static-to-dynamic-error/[id]/page_client-reference-manifest.js',
'variable-revalidate/encoding/page_client-reference-manifest.js',
'variable-revalidate/no-store/page_client-reference-manifest.js',
'variable-revalidate-edge/body/page_client-reference-manifest.js',
'force-dynamic-prerender/[slug]/page_client-reference-manifest.js',
'ssr-auto/fetch-revalidate-zero/page_client-reference-manifest.js',
'force-dynamic-no-prerender/[id]/page_client-reference-manifest.js',
'variable-revalidate/post-method/page_client-reference-manifest.js',
'variable-revalidate/status-code/page_client-reference-manifest.js',
'dynamic-no-gen-params-ssr/[slug]/page_client-reference-manifest.js',
'partial-gen-params/[lang]/[slug]/page_client-reference-manifest.js',
'variable-revalidate/revalidate-3/page_client-reference-manifest.js',
'variable-revalidate-edge/encoding/page_client-reference-manifest.js',
'variable-revalidate-edge/no-store/page_client-reference-manifest.js',
'variable-revalidate/authorization/page_client-reference-manifest.js',
'variable-revalidate/revalidate-360/page_client-reference-manifest.js',
'static-to-dynamic-error-forced/[id]/page_client-reference-manifest.js',
'gen-params-dynamic-revalidate/[slug]/page_client-reference-manifest.js',
'hooks/use-search-params/force-static/page_client-reference-manifest.js',
'variable-revalidate-edge/post-method/page_client-reference-manifest.js',
'variable-revalidate/headers-instance/page_client-reference-manifest.js',
'hooks/use-search-params/with-suspense/page_client-reference-manifest.js',
'variable-revalidate-edge/revalidate-3/page_client-reference-manifest.js',
'variable-revalidate/revalidate-360-isr/page_client-reference-manifest.js',
'variable-config-revalidate/revalidate-3/page_client-reference-manifest.js',
'variable-revalidate/post-method-request/page_client-reference-manifest.js',
'force-dynamic-catch-all/[slug]/[[...id]]/page_client-reference-manifest.js',
'variable-revalidate-edge/post-method-request/page_client-reference-manifest.js',
'partial-gen-params-no-additional-lang/[lang]/[slug]/page_client-reference-manifest.js',
'partial-gen-params-no-additional-slug/[lang]/[slug]/page_client-reference-manifest.js',
].sort()
)
})
it('should have correct prerender-manifest entries', async () => {
const curManifest = JSON.parse(JSON.stringify(prerenderManifest))
for (const key of Object.keys(curManifest.dynamicRoutes)) {
const item = curManifest.dynamicRoutes[key]
if (item.dataRouteRegex) {
item.dataRouteRegex = normalizeRegEx(item.dataRouteRegex)
}
if (item.routeRegex) {
item.routeRegex = normalizeRegEx(item.routeRegex)
}
}
for (const key of Object.keys(curManifest.routes)) {
const newKey = key.replace(
/partial-gen-params-no-additional-([\w]{1,})\/([\w]{1,})\/([\d]{1,})/,
'partial-gen-params-no-additional-$1/$2/RAND'
)
if (newKey !== key) {
const route = curManifest.routes[key]
delete curManifest.routes[key]
curManifest.routes[newKey] = {
...route,
dataRoute: `${newKey}.rsc`,
}
}
}
expect(curManifest.version).toBe(4)
expect(curManifest.routes).toEqual({
'/': {
initialRevalidateSeconds: false,
srcRoute: '/',
dataRoute: '/index.rsc',
},
'/blog/tim': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/tim.rsc',
},
'/blog/seb': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/seb.rsc',
},
'/blog/styfle': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/styfle.rsc',
},
'/blog/tim/first-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/tim/first-post.rsc',
},
'/blog/seb/second-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/seb/second-post.rsc',
},
'/blog/styfle/first-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/styfle/first-post.rsc',
},
'/blog/styfle/second-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/styfle/second-post.rsc',
},
'/force-cache': {
dataRoute: '/force-cache.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/force-cache',
},
'/hooks/use-pathname/slug': {
dataRoute: '/hooks/use-pathname/slug.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-pathname/[slug]',
},
'/hooks/use-search-params': {
dataRoute: '/hooks/use-search-params.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params',
},
'/hooks/use-search-params/force-static': {
dataRoute: '/hooks/use-search-params/force-static.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params/force-static',
},
'/hooks/use-search-params/with-suspense': {
dataRoute: '/hooks/use-search-params/with-suspense.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params/with-suspense',
},
'/partial-gen-params-no-additional-lang/en/RAND': {
dataRoute: '/partial-gen-params-no-additional-lang/en/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/en/first': {
dataRoute: '/partial-gen-params-no-additional-lang/en/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/en/second': {
dataRoute: '/partial-gen-params-no-additional-lang/en/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/RAND': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/first': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/second': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/RAND': {
dataRoute: '/partial-gen-params-no-additional-slug/en/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/first': {
dataRoute: '/partial-gen-params-no-additional-slug/en/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/second': {
dataRoute: '/partial-gen-params-no-additional-slug/en/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/RAND': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/first': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/second': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/ssg-draft-mode': {
dataRoute: '/ssg-draft-mode.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-draft-mode/[[...route]]',
},
'/ssg-draft-mode/test': {
dataRoute: '/ssg-draft-mode/test.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-draft-mode/[[...route]]',
},
'/ssg-draft-mode/test-2': {
dataRoute: '/ssg-draft-mode/test-2.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-draft-mode/[[...route]]',
},
'/force-static/first': {
dataRoute: '/force-static/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/force-static/[slug]',
},
'/force-static/second': {
dataRoute: '/force-static/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/force-static/[slug]',
},
'/gen-params-dynamic-revalidate/one': {
dataRoute: '/gen-params-dynamic-revalidate/one.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/gen-params-dynamic-revalidate/[slug]',
},
'/route-handler/revalidate-360-isr': {
dataRoute: null,
initialHeaders: {
'content-type': 'application/json',
'x-next-cache-tags':
'thankyounext,/route-handler/revalidate-360-isr/route',
},
initialRevalidateSeconds: 10,
srcRoute: '/route-handler/revalidate-360-isr',
},
'/route-handler/static-cookies': {
dataRoute: null,
initialHeaders: {
'set-cookie': 'theme=light; Path=/,my_company=ACME; Path=/',
'x-next-cache-tags': '/route-handler/static-cookies/route',
},
initialRevalidateSeconds: false,
srcRoute: '/route-handler/static-cookies',
},
'/variable-config-revalidate/revalidate-3': {
dataRoute: '/variable-config-revalidate/revalidate-3.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-config-revalidate/revalidate-3',
},
'/variable-revalidate/authorization': {
dataRoute: '/variable-revalidate/authorization.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/authorization',
},
'/variable-revalidate/cookie': {
dataRoute: '/variable-revalidate/cookie.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/cookie',
},
'/variable-revalidate/encoding': {
dataRoute: '/variable-revalidate/encoding.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/encoding',
},
'/variable-revalidate/headers-instance': {
dataRoute: '/variable-revalidate/headers-instance.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/headers-instance',
},
'/variable-revalidate/post-method': {
dataRoute: '/variable-revalidate/post-method.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/post-method',
},
'/variable-revalidate/revalidate-3': {
dataRoute: '/variable-revalidate/revalidate-3.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/revalidate-3',
},
'/variable-revalidate/revalidate-360-isr': {
dataRoute: '/variable-revalidate/revalidate-360-isr.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/revalidate-360-isr',
},
})
expect(curManifest.dynamicRoutes).toEqual({
'/blog/[author]/[slug]': {
routeRegex: normalizeRegEx('^/blog/([^/]+?)/([^/]+?)(?:/)?$'),
dataRoute: '/blog/[author]/[slug].rsc',
fallback: null,
dataRouteRegex: normalizeRegEx('^/blog/([^/]+?)/([^/]+?)\\.rsc$'),
},
'/blog/[author]': {
dataRoute: '/blog/[author].rsc',
dataRouteRegex: normalizeRegEx('^\\/blog\\/([^\\/]+?)\\.rsc$'),
fallback: false,
routeRegex: normalizeRegEx('^\\/blog\\/([^\\/]+?)(?:\\/)?$'),
},
'/dynamic-error/[id]': {
dataRoute: '/dynamic-error/[id].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/dynamic\\-error\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/dynamic\\-error\\/([^\\/]+?)(?:\\/)?$'
),
},
'/gen-params-dynamic-revalidate/[slug]': {
dataRoute: '/gen-params-dynamic-revalidate/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/gen\\-params\\-dynamic\\-revalidate\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/gen\\-params\\-dynamic\\-revalidate\\/([^\\/]+?)(?:\\/)?$'
),
},
'/hooks/use-pathname/[slug]': {
dataRoute: '/hooks/use-pathname/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/hooks\\/use\\-pathname\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/hooks\\/use\\-pathname\\/([^\\/]+?)(?:\\/)?$'
),
},
'/partial-gen-params-no-additional-lang/[lang]/[slug]': {
dataRoute:
'/partial-gen-params-no-additional-lang/[lang]/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-lang\\/([^\\/]+?)\\/([^\\/]+?)\\.rsc$'
),
fallback: false,
routeRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-lang\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
),
},
'/partial-gen-params-no-additional-slug/[lang]/[slug]': {
dataRoute:
'/partial-gen-params-no-additional-slug/[lang]/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-slug\\/([^\\/]+?)\\/([^\\/]+?)\\.rsc$'
),
fallback: false,
routeRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-slug\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
),
},
'/ssg-draft-mode/[[...route]]': {
dataRoute: '/ssg-draft-mode/[[...route]].rsc',
dataRouteRegex: '^\\/ssg\\-draft\\-mode(?:\\/(.+?))?\\.rsc$',
fallback: null,
routeRegex: '^\\/ssg\\-draft\\-mode(?:\\/(.+?))?(?:\\/)?$',
},
'/force-static/[slug]': {
dataRoute: '/force-static/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/force\\-static\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/force\\-static\\/([^\\/]+?)(?:\\/)?$'
),
},
'/static-to-dynamic-error-forced/[id]': {
dataRoute: '/static-to-dynamic-error-forced/[id].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)(?:\\/)?$'
),
},
})
})
it('should output debug info for static bailouts', async () => {
const cleanedOutput = stripAnsi(next.cliOutput)
expect(cleanedOutput).toContain(
'Static generation failed due to dynamic usage on /force-static, reason: headers'
)
expect(cleanedOutput).toContain(
'Static generation failed due to dynamic usage on /ssr-auto/cache-no-store, reason: no-store fetch'
)
})
}
it('should cache correctly for fetchCache = default-cache', async () => {
const res = await next.fetch('/default-cache')
expect(res.status).toBe(200)
let prevHtml = await res.text()
let prev$ = cheerio.load(prevHtml)
await check(async () => {
const curRes = await next.fetch('/default-cache')
expect(curRes.status).toBe(200)
const curHtml = await curRes.text()
const cur$ = cheerio.load(curHtml)
try {
expect(cur$('#data-no-cache').text()).not.toBe(
prev$('#data-no-cache').text()
)
expect(cur$('#data-force-cache').text()).toBe(
prev$('#data-force-cache').text()
)
expect(cur$('#data-revalidate-cache').text()).toBe(
prev$('#data-revalidate-cache').text()
)
expect(cur$('#data-revalidate-and-fetch-cache').text()).toBe(
prev$('#data-revalidate-and-fetch-cache').text()
)
expect(cur$('#data-revalidate-and-fetch-cache').text()).toBe(
prev$('#data-revalidate-and-fetch-cache').text()
)
} finally {
prevHtml = curHtml
prev$ = cur$
}
return 'success'
}, 'success')
})
it('should cache correctly for fetchCache = force-cache', async () => {
const res = await next.fetch('/force-cache')
expect(res.status).toBe(200)
let prevHtml = await res.text()
let prev$ = cheerio.load(prevHtml)
await check(async () => {
const curRes = await next.fetch('/force-cache')
expect(curRes.status).toBe(200)
const curHtml = await curRes.text()
const cur$ = cheerio.load(curHtml)
expect(cur$('#data-no-cache').text()).toBe(
prev$('#data-no-cache').text()
)
expect(cur$('#data-force-cache').text()).toBe(
prev$('#data-force-cache').text()
)
expect(cur$('#data-revalidate-cache').text()).toBe(
prev$('#data-revalidate-cache').text()
)
expect(cur$('#data-revalidate-and-fetch-cache').text()).toBe(
prev$('#data-revalidate-and-fetch-cache').text()
)
expect(cur$('#data-auto-cache').text()).toBe(
prev$('#data-auto-cache').text()
)
return 'success'
}, 'success')
if (!isNextDeploy) {
expect(next.cliOutput).toContain(
'Warning: fetch for https://next-data-api-endpoint.vercel.app/api/random?d4 on /force-cache specified "cache: force-cache" and "revalidate: 3", only one should be specified.'
)
}
})
it('should cache correctly for cache: no-store', async () => {
const res = await next.fetch('/fetch-no-cache')
expect(res.status).toBe(200)
let prevHtml = await res.text()
let prev$ = cheerio.load(prevHtml)
await check(async () => {
const curRes = await next.fetch('/fetch-no-cache')
expect(curRes.status).toBe(200)
const curHtml = await curRes.text()
const cur$ = cheerio.load(curHtml)
try {
expect(cur$('#data-no-cache').text()).not.toBe(
prev$('#data-no-cache').text()
)
expect(cur$('#data-force-cache').text()).toBe(
prev$('#data-force-cache').text()
)
expect(cur$('#data-revalidate-cache').text()).toBe(
prev$('#data-revalidate-cache').text()
)
expect(cur$('#data-revalidate-and-fetch-cache').text()).toBe(
prev$('#data-revalidate-and-fetch-cache').text()
)
expect(cur$('#data-auto-cache').text()).not.toBe(
prev$('#data-auto-cache').text()
)
} finally {
prevHtml = curHtml
prev$ = cur$
}
return 'success'
}, 'success')
})
if (isDev) {
it('should bypass fetch cache with cache-control: no-cache', async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3',
undefined,
{
headers: {
'cache-control': 'no-cache',
},
}
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).not.toBe(layoutData)
expect($2('#page-data').text()).not.toBe(pageData)
})
} else {
it('should not error with dynamic server usage with force-static', async () => {
const res = await next.fetch(
'/static-to-dynamic-error-forced/static-bailout-1'
)
const outputIndex = next.cliOutput.length
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('/static-to-dynamic-error-forced')
expect(html).toMatch(/id:.*?static-bailout-1/)
if (isNextStart) {
expect(stripAnsi(next.cliOutput).substring(outputIndex)).not.toMatch(
/Page changed from static to dynamic at runtime \/static-to-dynamic-error-forced\/static-bailout-1, reason: cookies/
)
}
})
it('should produce response with url from fetch', async () => {
const res = await next.fetch('/response-url')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('#data-url-default-cache').text()).toBe(
'https://next-data-api-endpoint.vercel.app/api/random?a1'
)
expect($('#data-url-no-cache').text()).toBe(
'https://next-data-api-endpoint.vercel.app/api/random?b2'
)
expect($('#data-url-cached').text()).toBe(
'https://next-data-api-endpoint.vercel.app/api/random?a1'
)
expect($('#data-value-default-cache').text()).toBe(
$('#data-value-cached').text()
)
})
it('should properly error when dynamic = "error" page uses dynamic', async () => {
const res = await next.fetch('/dynamic-error/static-bailout-1')
const outputIndex = next.cliOutput.length
expect(res.status).toBe(500)
if (isNextStart) {
expect(stripAnsi(next.cliOutput).substring(outputIndex)).not.toMatch(
/Page with dynamic = "error" encountered dynamic data method on \/dynamic-error\/static-bailout-1/
)
}
})
}
it('should skip cache in draft mode', async () => {
const draftRes = await next.fetch('/api/draft-mode?status=enable')
const setCookie = draftRes.headers.get('set-cookie')
const cookieHeader = { Cookie: setCookie?.split(';')[0] }
expect(cookieHeader.Cookie).toBeTruthy()
const res = await next.fetch('/ssg-draft-mode/test-1', {
headers: cookieHeader,
})
const html = await res.text()
const $ = cheerio.load(html)
const data1 = $('#data').text()
expect(data1).toBeTruthy()
expect(JSON.parse($('#draft-mode').text())).toEqual({ isEnabled: true })
const res2 = await next.fetch('/ssg-draft-mode/test-1', {
headers: cookieHeader,
})
const html2 = await res2.text()
const $2 = cheerio.load(html2)
const data2 = $2('#data').text()
expect(data2).toBeTruthy()
expect(data1).not.toBe(data2)
expect(JSON.parse($2('#draft-mode').text())).toEqual({ isEnabled: true })
})
it('should handle partial-gen-params with default dynamicParams correctly', async () => {
const res = await next.fetch('/partial-gen-params/en/first')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const params = JSON.parse($('#params').text())
expect(params).toEqual({ lang: 'en', slug: 'first' })
})
it('should handle partial-gen-params with layout dynamicParams = false correctly', async () => {
for (const { path, status, params } of [
// these checks don't work with custom memory only
// cache handler
...(process.env.CUSTOM_CACHE_HANDLER
? []
: [
{
path: '/partial-gen-params-no-additional-lang/en/first',
status: 200,
params: { lang: 'en', slug: 'first' },
},
]),
{
path: '/partial-gen-params-no-additional-lang/de/first',
status: 404,
params: {},
},
{
path: '/partial-gen-params-no-additional-lang/en/non-existent',
status: 404,
params: {},
},
]) {
const res = await next.fetch(path)
expect(res.status).toBe(status)
const html = await res.text()
const $ = cheerio.load(html)
const curParams = JSON.parse($('#params').text() || '{}')
expect(curParams).toEqual(params)
}
})
it('should handle partial-gen-params with page dynamicParams = false correctly', async () => {
for (const { path, status, params } of [
// these checks don't work with custom memory only
// cache handler
...(process.env.CUSTOM_CACHE_HANDLER
? []
: [
{
path: '/partial-gen-params-no-additional-slug/en/first',
status: 200,
params: { lang: 'en', slug: 'first' },
},
]),
{
path: '/partial-gen-params-no-additional-slug/de/first',
status: 404,
params: {},
},
{
path: '/partial-gen-params-no-additional-slug/en/non-existent',
status: 404,
params: {},
},
]) {
const res = await next.fetch(path)
expect(res.status).toBe(status)
const html = await res.text()
const $ = cheerio.load(html)
const curParams = JSON.parse($('#params').text() || '{}')
expect(curParams).toEqual(params)
}
})
// fetch cache in generateStaticParams needs fs for persistence
// so doesn't behave as expected with custom in memory only
// cache handler
if (!process.env.CUSTOM_CACHE_HANDLER) {
it('should honor fetch cache in generateStaticParams', async () => {
const initialRes = await next.fetch(
`/partial-gen-params-no-additional-lang/en/first`
)
expect(initialRes.status).toBe(200)
// we can't read prerender-manifest from deployment
if (isNextDeploy) return
let langFetchSlug
let slugFetchSlug
if (isDev) {
await check(() => {
const matches = stripAnsi(next.cliOutput).match(
/partial-gen-params fetch ([\d]{1,})/
)
if (matches[1]) {
langFetchSlug = matches[1]
slugFetchSlug = langFetchSlug
}
return langFetchSlug ? 'success' : next.cliOutput
}, 'success')
} else {
// the fetch cache can potentially be a miss since
// the generateStaticParams are executed parallel
// in separate workers so parse value from
// prerender-manifest
const routes = Object.keys(prerenderManifest.routes)
for (const route of routes) {
const langSlug = route.match(
/partial-gen-params-no-additional-lang\/en\/([\d]{1,})/
)?.[1]
if (langSlug) {
langFetchSlug = langSlug
}
const slugSlug = route.match(
/partial-gen-params-no-additional-slug\/en\/([\d]{1,})/
)?.[1]
if (slugSlug) {
slugFetchSlug = slugSlug
}
}
}
require('console').log({ langFetchSlug, slugFetchSlug })
for (const { pathname, slug } of [
{
pathname: '/partial-gen-params-no-additional-lang/en',
slug: langFetchSlug,
},
{
pathname: '/partial-gen-params-no-additional-slug/en',
slug: slugFetchSlug,
},
]) {
const res = await next.fetch(`${pathname}/${slug}`)
expect(res.status).toBe(200)
expect(
JSON.parse(
cheerio
.load(await res.text())('#params')
.text()
)
).toEqual({ lang: 'en', slug })
}
})
}
it('should honor fetch cache correctly', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
if (isNextStart) {
expect(next.cliOutput).toContain(
`Page "/variable-revalidate-edge/revalidate-3" is using runtime = 'edge' which is currently incompatible with dynamic = 'force-static'. Please remove either "runtime" or "force-static" for correct behavior`
)
}
})
it('should honor fetch cache correctly (edge)', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
// the test cache handler is simple and doesn't share
// state across workers so not guaranteed to have cache hit
if (!(isNextDeploy && process.env.CUSTOM_CACHE_HANDLER)) {
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/revalidate-3'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
}
return 'success'
}, 'success')
})
it('should cache correctly with authorization header and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/authorization'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/authorization'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should not cache correctly with POST method request init', async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method-request'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const pageData2 = $('#page-data2').text()
for (let i = 0; i < 3; i++) {
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method-request'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#page-data2').text()).not.toBe(pageData2)
}
})
it('should cache correctly with post method and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const dataBody1 = $('#data-body1').text()
const dataBody2 = $('#data-body2').text()
const dataBody3 = $('#data-body3').text()
const dataBody4 = $('#data-body4').text()
expect(dataBody1).not.toBe(dataBody2)
expect(dataBody2).not.toBe(dataBody3)
expect(dataBody3).not.toBe(dataBody4)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
expect($2('#data-body1').text()).toBe(dataBody1)
expect($2('#data-body2').text()).toBe(dataBody2)
expect($2('#data-body3').text()).toBe(dataBody3)
return 'success'
}, 'success')
})
it('should cache correctly with post method and revalidate edge', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const dataBody1 = $('#data-body1').text()
const dataBody2 = $('#data-body2').text()
const dataBody3 = $('#data-body3').text()
const dataBody4 = $('#data-body4').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
expect($2('#data-body1').text()).toBe(dataBody1)
expect($2('#data-body2').text()).toBe(dataBody2)
expect($2('#data-body3').text()).toBe(dataBody3)
expect($2('#data-body4').text()).toBe(dataBody4)
return 'success'
}, 'success')
})
it('should cache correctly with POST method and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with cookie header and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(next.url, '/variable-revalidate/cookie')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(next.url, '/variable-revalidate/cookie')
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with utf8 encoding', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/encoding'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
expect(JSON.parse(pageData).jp).toBe(
'超鬼畜激辛ボム兵スピンジャンプ Bomb Spin Jump'
)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/encoding'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with utf8 encoding edge', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/encoding'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
expect(JSON.parse(pageData).jp).toBe(
'超鬼畜激辛ボム兵スピンジャンプ Bomb Spin Jump'
)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/encoding'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly handle JSON body', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/body'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
expect(pageData).toBe('{"hello":"world"}')
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/body'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should not throw Dynamic Server Usage error when using generateStaticParams with draftMode', async () => {
const browserOnIndexPage = await next.browser('/ssg-draft-mode')
const content = await browserOnIndexPage
.elementByCss('#draft-mode')
.text()
expect(content).toBe('{"isEnabled":false}')
})
it('should force SSR correctly for headers usage', async () => {
const res = await next.fetch('/force-static', {
headers: {
Cookie: 'myCookie=cookieValue',
another: 'header',
},
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#headers').text())).toIncludeAllMembers([
'cookie',
'another',
])
expect(JSON.parse($('#cookies').text())).toEqual([
{
name: 'myCookie',
value: 'cookieValue',
},
])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).not.toBe($2('#now').text())
}
})
it('should allow dynamic routes to access cookies', async () => {
for (const slug of ['books', 'frameworks']) {
for (let i = 0; i < 2; i++) {
let $ = await next.render$(
`/force-dynamic-prerender/${slug}`,
{},
{ headers: { cookie: 'session=value' } }
)
expect($('#slug').text()).toBe(slug)
expect($('#cookie-result').text()).toBe('has cookie')
$ = await next.render$(`/force-dynamic-prerender/${slug}`)
expect($('#slug').text()).toBe(slug)
expect($('#cookie-result').text()).toBe('no cookie')
}
}
})
it('should not error with generateStaticParams and dynamic data', async () => {
const res = await next.fetch('/gen-params-dynamic/one')
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('gen-params-dynamic/[slug]')
expect(html).toContain('one')
const data = cheerio.load(html)('#data').text()
for (let i = 0; i < 5; i++) {
const res2 = await next.fetch('/gen-params-dynamic/one')
expect(res2.status).toBe(200)
expect(
cheerio
.load(await res2.text())('#data')
.text()
).not.toBe(data)
}
})
it('should not error with force-dynamic and catch-all routes', async () => {
// Regression test for https://github.com/vercel/next.js/issues/45603
const res = await next.fetch('/force-dynamic-catch-all/slug/a')
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('Dynamic catch-all route')
})
it('should not error with generateStaticParams and authed data on revalidate', async () => {
const res = await next.fetch('/gen-params-dynamic-revalidate/one')
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('gen-params-dynamic/[slug]')
expect(html).toContain('one')
const initData = cheerio.load(html)('#data').text()
await check(async () => {
const res2 = await next.fetch('/gen-params-dynamic-revalidate/one')
expect(res2.status).toBe(200)
const $ = cheerio.load(await res2.text())
expect($('#data').text()).toBeTruthy()
expect($('#data').text()).not.toBe(initData)
return 'success'
}, 'success')
})
if (!process.env.CUSTOM_CACHE_HANDLER) {
it('should honor dynamic = "force-static" correctly', async () => {
const res = await next.fetch('/force-static/first')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({ slug: 'first' })
expect(JSON.parse($('#headers').text())).toEqual([])
expect(JSON.parse($('#cookies').text())).toEqual([])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static/first')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).toBe($2('#now').text())
}
})
it('should honor dynamic = "force-static" correctly (lazy)', async () => {
const res = await next.fetch('/force-static/random')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({ slug: 'random' })
expect(JSON.parse($('#headers').text())).toEqual([])
expect(JSON.parse($('#cookies').text())).toEqual([])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static/random')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).toBe($2('#now').text())
}
})
}
// since we aren't leveraging fs cache with custom handler
// then these will 404 as they are cache misses
if (!(isNextStart && process.env.CUSTOM_CACHE_HANDLER)) {
it('should handle dynamicParams: false correctly', async () => {
const validParams = ['tim', 'seb', 'styfle']
for (const param of validParams) {
const res = await next.fetch(`/blog/${param}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({
author: param,
})
expect($('#page').text()).toBe('/blog/[author]')
}
const invalidParams = ['timm', 'non-existent']
for (const param of invalidParams) {
const invalidRes = await next.fetch(`/blog/${param}`, {
redirect: 'manual',
})
expect(invalidRes.status).toBe(404)
expect(await invalidRes.text()).toContain('page could not be found')
}
})
}
it('should work with forced dynamic path', async () => {
for (const slug of ['first', 'second']) {
const res = await next.fetch(`/dynamic-no-gen-params-ssr/${slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(await res.text()).toContain(`${slug}`)
}
})
it('should work with dynamic path no generateStaticParams', async () => {
for (const slug of ['first', 'second']) {
const res = await next.fetch(`/dynamic-no-gen-params/${slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(await res.text()).toContain(`${slug}`)
}
})
it('should handle dynamicParams: true correctly', async () => {
const paramsToCheck = [
{
author: 'tim',
slug: 'first-post',
},
{
author: 'seb',
slug: 'second-post',
},
{
author: 'styfle',
slug: 'first-post',
},
{
author: 'new-author',
slug: 'first-post',
},
]
for (const params of paramsToCheck) {
const res = await next.fetch(`/blog/${params.author}/${params.slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual(params)
expect($('#page').text()).toBe('/blog/[author]/[slug]')
}
})
// since we aren't leveraging fs cache with custom handler
// then these will 404 as they are cache misses
if (!(isNextStart && process.env.CUSTOM_CACHE_HANDLER)) {
it('should navigate to static path correctly', async () => {
const browser = await next.browser('/blog/tim')
await browser.eval('window.beforeNav = 1')
expect(
await browser.eval('document.documentElement.innerHTML')
).toContain('/blog/[author]')
await browser.elementByCss('#author-2').click()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'seb' ? 'found' : params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
await browser.elementByCss('#author-1-post-1').click()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'tim' && params.slug === 'first-post'
? 'found'
: params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
await browser.back()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'seb' ? 'found' : params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
})
}
it('should ssr dynamically when detected automatically with fetch cache option', async () => {
const pathname = '/ssr-auto/cache-no-store'
const initialRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
expect(initial$('#page').text()).toBe(pathname)
const initialDate = initial$('#date').text()
expect(initialHtml).toContain('Example Domain')
const secondRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(secondRes.status).toBe(200)
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe(pathname)
const secondDate = second$('#date').text()
expect(secondHtml).toContain('Example Domain')
expect(secondDate).not.toBe(initialDate)
})
it('should render not found pages correctly and fallback to the default one', async () => {
const res = await next.fetch(`/blog/shu/hi`, {
redirect: 'manual',
})
expect(res.status).toBe(404)
const html = await res.text()
expect(html).toInclude('"noindex"')
expect(html).toInclude('This page could not be found.')
})
// TODO-APP: support fetch revalidate case for dynamic rendering
it.skip('should ssr dynamically when detected automatically with fetch revalidate option', async () => {
const pathname = '/ssr-auto/fetch-revalidate-zero'
const initialRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
expect(initial$('#page').text()).toBe(pathname)
const initialDate = initial$('#date').text()
expect(initialHtml).toContain('Example Domain')
const secondRes = await next.fetch(pathname, {
redirect: 'manual',
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
})
expect(secondRes.status).toBe(200)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe(pathname)
const secondDate = second$('#date').text()
expect(secondHtml).toContain('Example Domain')
expect(secondDate).not.toBe(initialDate)
})
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
it('should ssr dynamically when forced via config', async () => {
const initialRes = await next.fetch('/ssr-forced', {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
expect(initial$('#page').text()).toBe('/ssr-forced')
const initialDate = initial$('#date').text()
const secondRes = await next.fetch('/ssr-forced', {
redirect: 'manual',
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
})
expect(secondRes.status).toBe(200)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe('/ssr-forced')
const secondDate = second$('#date').text()
expect(secondDate).not.toBe(initialDate)
})
describe('useSearchParams', () => {
describe('client', () => {
it('should bailout to client rendering - without suspense boundary', async () => {
const browser = await next.browser(
'/hooks/use-search-params?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe(
'value'
)
expect(await browser.elementByCss('#params-second').text()).toBe(
'other'
)
expect(await browser.elementByCss('#params-third').text()).toBe('')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
it('should bailout to client rendering - with suspense boundary', async () => {
const browser = await next.browser(
'/hooks/use-search-params/with-suspense?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe(
'value'
)
expect(await browser.elementByCss('#params-second').text()).toBe(
'other'
)
expect(await browser.elementByCss('#params-third').text()).toBe('')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
)
})
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
it.skip('should have empty search params on force-static', async () => {
const browser = await next.browser(
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
'/hooks/use-search-params/force-static?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe('N/A')
expect(await browser.elementByCss('#params-second').text()).toBe(
'N/A'
)
expect(await browser.elementByCss('#params-third').text()).toBe('N/A')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
await browser.elementById('to-use-search-params').click()
await browser.waitForElementByCss('#hooks-use-search-params')
// Should not be empty after navigating to another page with useSearchParams
expect(await browser.elementByCss('#params-first').text()).toBe('1')
expect(await browser.elementByCss('#params-second').text()).toBe('2')
expect(await browser.elementByCss('#params-third').text()).toBe('3')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
})
// TODO-APP: re-enable after investigating rewrite params
if (!(global as any).isNextDeploy) {
it('should have values from canonical url on rewrite', async () => {
const browser = await next.browser(
'/rewritten-use-search-params?first=a&second=b&third=c'
)
expect(await browser.elementByCss('#params-first').text()).toBe('a')
expect(await browser.elementByCss('#params-second').text()).toBe(
'b'
)
expect(await browser.elementByCss('#params-third').text()).toBe('c')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
}
})
// Don't run these tests in dev mode since they won't be statically generated
if (!isDev) {
describe('server response', () => {
it('should bailout to client rendering - without suspense boundary', async () => {
const res = await next.fetch('/hooks/use-search-params')
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
it('should bailout to client rendering - with suspense boundary', async () => {
const res = await next.fetch(
'/hooks/use-search-params/with-suspense'
)
const html = await res.text()
expect(html).toInclude('<p>search params suspense</p>')
})
it.skip('should have empty search params on force-static', async () => {
const res = await next.fetch(
'/hooks/use-search-params/force-static?first=value&second=other&third'
)
const html = await res.text()
// Shouild not bail out to client rendering
expect(html).not.toInclude('<p>search params suspense</p>')
// Use empty search params instead
const $ = cheerio.load(html)
expect($('#params-first').text()).toBe('N/A')
expect($('#params-second').text()).toBe('N/A')
expect($('#params-third').text()).toBe('N/A')
expect($('#params-not-real').text()).toBe('N/A')
})
})
}
})
// TODO: needs updating as usePathname should not bail
describe.skip('usePathname', () => {
if (isDev) {
it('should bail out to client rendering during SSG', async () => {
const res = await next.fetch('/hooks/use-pathname/slug')
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
}
it('should have the correct values', async () => {
const browser = await next.browser('/hooks/use-pathname/slug')
expect(await browser.elementByCss('#pathname').text()).toBe(
'/hooks/use-pathname/slug'
)
})
it('should have values from canonical url on rewrite', async () => {
const browser = await next.browser('/rewritten-use-pathname')
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
expect(await browser.elementByCss('#pathname').text()).toBe(
'/rewritten-use-pathname'
)
})
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
})
it('should keep querystring on static page', async () => {
const browser = await next.browser('/blog/tim?message=hello-world')
const checkUrl = async () =>
expect(await browser.url()).toBe(
next.url + '/blog/tim?message=hello-world'
)
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
checkUrl()
await waitFor(1000)
checkUrl()
})
if (process.env.CUSTOM_CACHE_HANDLER && !isNextDeploy) {
it('should have logs from cache-handler', () => {
expect(next.cliOutput).toContain('initialized custom cache-handler')
expect(next.cliOutput).toContain('cache-handler get')
expect(next.cliOutput).toContain('cache-handler set')
})
}
useSearchParams - bailout to client rendering during static generation (#43603) Currently `useSearchParams` bails out of static generation altogether, forcing the page to be dynamic. This behaviour is wrong. Instead it should still be statically generated, but `useSearchParams` should only run on the client. This is achieved by throwing a "bailout to client rendering" error. If there's no suspense boundary the whole page will bailout to be rendered on the client. If there is a suspense boundary it will only bailout from that point. ~This PR also adds handling for `export const dynamic = 'force-static'` combined with `useSearchParams`. If it is enabled it will return an empty `ReadonlyURLSearchParams` and skip the bailout to client rendering. Since the `staticGenerationAsyncStorage` only is available on the server - `forceStatic` is sent to the `app-router` to enable sending an empty `URLSearchParams` to match the server response.~ https://github.com/vercel/next.js/pull/43603#discussion_r1042071542 the implementation was wrong, added skipped tests and todo comment for now. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-12-12 12:53:38 +01:00
}
)