Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
253 lines
9.1 KiB
TypeScript
253 lines
9.1 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import * as cheerio from 'cheerio'
|
|
import { getCacheHeader, retry } from 'next-test-utils'
|
|
import { computeCacheBustingSearchParam } from 'next/dist/shared/lib/router/utils/cache-busting-search-param'
|
|
|
|
describe('middleware-static-rewrite', () => {
|
|
const { next, isNextDeploy, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
if (isNextDev) {
|
|
it.skip('skipping dev test', () => {})
|
|
return
|
|
}
|
|
|
|
if (process.env.__NEXT_CACHE_COMPONENTS === 'true') {
|
|
// Here we're validating that the correct fallback shell was used for
|
|
// rendering.
|
|
it('should use the correct fallback route', async () => {
|
|
// First try to load a page that'll use the base fallback route with the
|
|
// `/[first]/[second]/[third]` fallback.
|
|
let $ = await next.render$('/first/second/third')
|
|
|
|
expect($('[data-slug]').data('slug')).toBe('first/second/third')
|
|
|
|
// Get the sentinel value that was generated at build time or runtime.
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]/[second]"]').data('sentinel')).toBe(
|
|
'buildtime'
|
|
)
|
|
expect(
|
|
$('[data-layout="/[first]/[second]/[third]"]').data('sentinel')
|
|
).toBe('buildtime')
|
|
|
|
// Then we try to load a page that'll use the `/first/second/[third]`
|
|
// fallback.
|
|
$ = await next.render$('/first/second/not-third')
|
|
|
|
expect($('[data-slug]').data('slug')).toBe('first/second/not-third')
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]/[second]"]').data('sentinel')).toBe(
|
|
'buildtime'
|
|
)
|
|
expect(
|
|
$('[data-layout="/[first]/[second]/[third]"]').data('sentinel')
|
|
).toBe('runtime')
|
|
|
|
// Then we try to load a page that'll use the `/first/[second]/[third]`
|
|
$ = await next.render$('/first/not-second/not-third')
|
|
|
|
expect($('[data-slug]').data('slug')).toBe('first/not-second/not-third')
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]/[second]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
expect(
|
|
$('[data-layout="/[first]/[second]/[third]"]').data('sentinel')
|
|
).toBe('runtime')
|
|
|
|
// Then we try to load a page that'll use the `/[first]/[second]/[third]`
|
|
$ = await next.render$('/not-first/not-second/not-third')
|
|
|
|
expect($('[data-slug]').data('slug')).toBe(
|
|
'not-first/not-second/not-third'
|
|
)
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/[first]"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/[first]/[second]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
expect(
|
|
$('[data-layout="/[first]/[second]/[third]"]').data('sentinel')
|
|
).toBe('runtime')
|
|
})
|
|
|
|
it('should handle middleware rewrites as well', async () => {
|
|
let res = await next.fetch('/not-broken')
|
|
|
|
expect(res.status).toBe(200)
|
|
|
|
if (isNextDeploy) {
|
|
// We produced a partial fallback shell for rewrite/[slug], so we shouldn't see a cache HIT.
|
|
expect(getCacheHeader(res)).toMatch(/MISS|PRERENDER/)
|
|
} else {
|
|
expect(res.headers.get('x-nextjs-cache')).toBe(null)
|
|
}
|
|
|
|
let html = await res.text()
|
|
let $ = cheerio.load(html)
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/rewrite"]').data('sentinel')).toBe('buildtime')
|
|
expect($('[data-layout="/rewrite/[slug]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
|
|
await retry(async () => {
|
|
res = await next.fetch('/not-broken')
|
|
|
|
expect(res.status).toBe(200)
|
|
if (isNextDeploy) {
|
|
expect(getCacheHeader(res)).toBe('HIT')
|
|
} else {
|
|
expect(res.headers.get('x-nextjs-cache')).toBe('HIT')
|
|
}
|
|
|
|
html = await res.text()
|
|
$ = cheerio.load(html)
|
|
|
|
expect($('[data-rewrite-slug]').data('rewrite-slug')).toBe('not-broken')
|
|
|
|
// With partial fallback upgrading, the cached entry upgrades from a
|
|
// fallback shell to a full route render.
|
|
// These assertions are part of the outer retry block because the fallback->route shell upgrade
|
|
// happens in the background. It's possible to see a cache HIT for the fallback shell before it
|
|
// switches to the full route shell.
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite/[slug]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should revalidate the overview page without replacing it with a 404', async () => {
|
|
const url = new URL('/my-team', 'http://localhost')
|
|
const rsc = computeCacheBustingSearchParam(
|
|
'1',
|
|
'/_head',
|
|
undefined,
|
|
undefined
|
|
)
|
|
|
|
url.searchParams.set('_rsc', rsc)
|
|
|
|
let res = await next.fetch(url.pathname + url.search, {
|
|
headers: {
|
|
Cookie: 'overview-param=grid',
|
|
RSC: '1',
|
|
'Next-Router-Prefetch': '1',
|
|
'Next-Router-Segment-Prefetch': '/_head',
|
|
},
|
|
})
|
|
|
|
// A 404 here represents a routing issue that was resolved by an upstream
|
|
// PR in the builder: https://github.com/vercel/vercel/pull/13927
|
|
expect(res.status).toBe(200)
|
|
|
|
// Now, let's verify that we both got rewritten to the correct page, and
|
|
// that we're being served the prerender shell.
|
|
expect(res.headers.get('x-nextjs-rewritten-path')).toBe(
|
|
'/my-team/~/overview/grid'
|
|
)
|
|
expect(res.headers.get('x-nextjs-postponed')).toBe('2')
|
|
expect(res.headers.get('x-nextjs-prerender')).toBe('1')
|
|
|
|
// Grab the RSC content.
|
|
const rsc1 = await res.text()
|
|
|
|
// Grab the title which includes the random number.
|
|
const title1 = rsc1.match(/Grid Page (\d+\.\d+)/)?.[1]
|
|
expect(title1).toBeDefined()
|
|
|
|
// Now, let's trigger a revalidation for the page.
|
|
res = await next.fetch(
|
|
'/api?path=/my-team&path=/my-team/~/overview/grid&path=/[first]/~/overview/grid'
|
|
)
|
|
|
|
// Now, let's keep polling the prefetch until it's revalidated.
|
|
let rsc2: string
|
|
await retry(async () => {
|
|
res = await next.fetch(url.pathname + url.search, {
|
|
headers: {
|
|
Cookie: 'overview-param=grid',
|
|
RSC: '1',
|
|
'Next-Router-Prefetch': '1',
|
|
'Next-Router-Segment-Prefetch': '/_head',
|
|
},
|
|
})
|
|
|
|
// A 404 here represents a routing issue that was resolved by an upstream
|
|
// PR in the builder: https://github.com/vercel/vercel/pull/13927
|
|
expect(res.status).toBe(200)
|
|
|
|
// We're expecting that the title has changed, so let's compare that the
|
|
// rsc payload is different.
|
|
rsc2 = await res.text()
|
|
expect(rsc1).not.toBe(rsc2)
|
|
})
|
|
|
|
// Now that the revalidation has been completed, let's also verify that
|
|
// it revalidated correctly.
|
|
expect(res.headers.get('x-nextjs-postponed')).toBe('2')
|
|
expect(res.headers.get('x-nextjs-rewritten-path')).toBe(
|
|
'/my-team/~/overview/grid'
|
|
)
|
|
|
|
// We expect that the only difference between the two RSC contents is the
|
|
// title.
|
|
const title2 = rsc2.match(/Grid Page (\d+\.\d+)/)?.[1]
|
|
expect(title2).toBeDefined()
|
|
expect(title2).not.toBe(title1)
|
|
|
|
// Let's compare the RSC contents, with the titles removed.
|
|
const cleaned1 = rsc1.replace(/Grid Page (\d+\.\d+)/, 'Grid Page')
|
|
const cleaned2 = rsc2.replace(/Grid Page (\d+\.\d+)/, 'Grid Page')
|
|
expect(cleaned1).toBe(cleaned2)
|
|
})
|
|
} else {
|
|
// Here we're validating that there is a static page generated for the
|
|
// rewritten path.
|
|
it('should eventually result in a cache hit', async () => {
|
|
let res = await next.fetch('/not-broken')
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(getCacheHeader(res)).toMatch(/MISS|HIT|PRERENDER/)
|
|
|
|
let html = await res.text()
|
|
let $ = cheerio.load(html)
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite/[slug]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
|
|
await retry(async () => {
|
|
res = await next.fetch('/not-broken')
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(getCacheHeader(res)).toBe('HIT')
|
|
})
|
|
|
|
html = await res.text()
|
|
$ = cheerio.load(html)
|
|
|
|
expect($('[data-rewrite-slug]').data('rewrite-slug')).toBe('not-broken')
|
|
|
|
expect($('[data-layout="/"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite"]').data('sentinel')).toBe('runtime')
|
|
expect($('[data-layout="/rewrite/[slug]"]').data('sentinel')).toBe(
|
|
'runtime'
|
|
)
|
|
})
|
|
}
|
|
})
|