Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Rename RSC and Router headers #42482

Merged
merged 11 commits into from Nov 8, 2022
9 changes: 9 additions & 0 deletions packages/next/build/index.ts
Expand Up @@ -123,6 +123,7 @@ import { RemotePattern } from '../shared/lib/image-config'
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import { RSC, RSC_VARY_HEADER } from '../client/components/app-router-headers'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -755,6 +756,10 @@ export default async function build(
defaultLocale: string
localeDetection?: false
}
rsc: {
header: typeof RSC
varyHeader: typeof RSC_VARY_HEADER
}
} = nextBuildSpan.traceChild('generate-routes-manifest').traceFn(() => {
const sortedRoutes = getSortedRoutes([
...pageKeys.pages,
Expand All @@ -781,6 +786,10 @@ export default async function build(
staticRoutes,
dataRoutes: [],
i18n: config.i18n || undefined,
rsc: {
header: RSC,
varyHeader: RSC_VARY_HEADER,
},
}
})

Expand Down
5 changes: 5 additions & 0 deletions packages/next/client/components/app-router-headers.ts
@@ -0,0 +1,5 @@
export const RSC = 'RSC' as const
export const NEXT_ROUTER_STATE_TREE = 'Next-Router-State-Tree' as const
export const NEXT_ROUTER_PREFETCH = 'Next-Router-Prefetch' as const
export const RSC_VARY_HEADER =
`${RSC}, ${NEXT_ROUTER_STATE_TREE}, ${NEXT_ROUTER_PREFETCH}` as const
17 changes: 11 additions & 6 deletions packages/next/client/components/app-router.tsx
Expand Up @@ -29,6 +29,11 @@ import {
} from '../../shared/lib/hooks-client-context'
import { useReducerWithReduxDevtools } from './use-reducer-with-devtools'
import { ErrorBoundary, GlobalErrorComponent } from './error-boundary'
import {
NEXT_ROUTER_PREFETCH,
NEXT_ROUTER_STATE_TREE,
RSC,
} from './app-router-headers'

function urlToUrlWithoutFlightMarker(url: string): URL {
const urlWithoutFlightParameters = new URL(url, location.origin)
Expand All @@ -53,18 +58,18 @@ export async function fetchServerResponse(
prefetch?: true
): Promise<[FlightData: FlightData, canonicalUrlOverride: URL | undefined]> {
const headers: {
__rsc__: '1'
__next_router_state_tree__: string
__next_router_prefetch__?: '1'
[RSC]: '1'
[NEXT_ROUTER_STATE_TREE]: string
[NEXT_ROUTER_PREFETCH]?: '1'
} = {
// Enable flight response
__rsc__: '1',
[RSC]: '1',
// Provide the current router state
__next_router_state_tree__: JSON.stringify(flightRouterState),
[NEXT_ROUTER_STATE_TREE]: JSON.stringify(flightRouterState),
}
if (prefetch) {
// Enable prefetch response
headers.__next_router_prefetch__ = '1'
headers[NEXT_ROUTER_PREFETCH] = '1'
}

const res = await fetch(url.toString(), {
Expand Down
26 changes: 17 additions & 9 deletions packages/next/server/app-render.tsx
Expand Up @@ -38,6 +38,11 @@ import { NOT_FOUND_ERROR_CODE } from '../client/components/not-found'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
import { Writable } from 'stream'
import stringHash from 'next/dist/compiled/string-hash'
import {
NEXT_ROUTER_PREFETCH,
NEXT_ROUTER_STATE_TREE,
RSC,
} from '../client/components/app-router-headers'

const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge'

Expand Down Expand Up @@ -686,16 +691,16 @@ function getScriptNonceFromHeader(cspHeaderValue: string): string | undefined {
return nonce
}

const FLIGHT_PARAMETERS = [
'__rsc__',
'__next_router_state_tree__',
'__next_router_prefetch__',
export const FLIGHT_PARAMETERS = [
[RSC],
[NEXT_ROUTER_STATE_TREE],
[NEXT_ROUTER_PREFETCH],
] as const

function headersWithoutFlight(headers: IncomingHttpHeaders) {
const newHeaders = { ...headers }
for (const param of FLIGHT_PARAMETERS) {
delete newHeaders[param]
delete newHeaders[param.toString().toLowerCase()]
}
return newHeaders
}
Expand Down Expand Up @@ -728,7 +733,7 @@ export async function renderToHTMLOrFlight(
*/
const isStaticGeneration =
renderOpts.supportsDynamicHTML !== true && !renderOpts.isBot
const isFlight = req.headers.__rsc__ !== undefined
const isFlight = req.headers[RSC.toLowerCase()] !== undefined

const capturedErrors: Error[] = []
const allCapturedErrors: Error[] = []
Expand Down Expand Up @@ -786,7 +791,8 @@ export async function renderToHTMLOrFlight(
// don't modify original query object
query = Object.assign({}, query)

const isPrefetch = req.headers.__next_router_prefetch__ !== undefined
const isPrefetch =
req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()] !== undefined

// TODO-APP: verify the tree is valid
// TODO-APP: verify query param is single value (not an array)
Expand All @@ -795,8 +801,10 @@ export async function renderToHTMLOrFlight(
* Router state provided from the client-side router. Used to handle rendering from the common layout down.
*/
let providedFlightRouterState: FlightRouterState = isFlight
? req.headers.__next_router_state_tree__
? JSON.parse(req.headers.__next_router_state_tree__ as string)
? req.headers[NEXT_ROUTER_STATE_TREE.toLowerCase()]
? JSON.parse(
req.headers[NEXT_ROUTER_STATE_TREE.toLowerCase()] as string
)
: undefined
: undefined

Expand Down
19 changes: 9 additions & 10 deletions packages/next/server/base-server.ts
Expand Up @@ -74,6 +74,8 @@ import { getHostname } from '../shared/lib/get-hostname'
import { parseUrl as parseUrlUtil } from '../shared/lib/router/utils/parse-url'
import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info'
import { MiddlewareMatcher } from '../build/analysis/get-page-static-info'
import { RSC, RSC_VARY_HEADER } from '../client/components/app-router-headers'
import { FLIGHT_PARAMETERS } from './app-render'

export type FindComponentsResult = {
components: LoadComponentsReturnType
Expand Down Expand Up @@ -1053,12 +1055,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
(isSSG || hasServerProps)

if (isAppPath) {
res.setHeader(
'vary',
'__rsc__, __next_router_state_tree__, __next_router_prefetch__'
)
res.setHeader('vary', RSC_VARY_HEADER)

if (isSSG && req.headers['__rsc__']) {
if (isSSG && req.headers[RSC.toLowerCase()]) {
if (!this.minimalMode) {
isDataReq = true
}
Expand All @@ -1067,9 +1066,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
opts.runtime !== 'experimental-edge' ||
(this.serverOptions as any).webServerConfig
) {
delete req.headers['__rsc__']
delete req.headers['__next_router_state_tree__']
delete req.headers['__next_router_prefetch__']
for (const param of FLIGHT_PARAMETERS) {
delete req.headers[param.toString().toLowerCase()]
}
}
}
}
Expand All @@ -1096,9 +1095,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
)
}

// Don't delete query.__rsc__ yet, it still needs to be used in renderToHTML later
// Don't delete headers[RSC] yet, it still needs to be used in renderToHTML later
const isFlightRequest = Boolean(
this.serverComponentManifest && req.headers.__rsc__
this.serverComponentManifest && req.headers[RSC.toLowerCase()]
)

// we need to ensure the status code if /404 is visited directly
Expand Down
5 changes: 0 additions & 5 deletions packages/next/server/internal-utils.ts
Expand Up @@ -5,11 +5,6 @@ const INTERNAL_QUERY_NAMES = [
'__nextLocale',
'__nextDefaultLocale',
'__nextIsNotFound',
// RSC
'__rsc__',
// Routing
'__next_router_state_tree__',
'__next_router_prefetch__',
] as const

const EXTENDED_INTERNAL_QUERY_NAMES = ['__nextDataReq'] as const
Expand Down
13 changes: 9 additions & 4 deletions packages/next/server/web/adapter.ts
Expand Up @@ -10,6 +10,11 @@ import { waitUntilSymbol } from './spec-extension/fetch-event'
import { NextURL } from './next-url'
import { stripInternalSearchParams } from '../internal-utils'
import { normalizeRscPath } from '../../shared/lib/router/utils/app-paths'
import {
NEXT_ROUTER_PREFETCH,
NEXT_ROUTER_STATE_TREE,
RSC,
} from '../../client/components/app-router-headers'

class NextRequestHint extends NextRequest {
sourcePage: string
Expand Down Expand Up @@ -37,9 +42,9 @@ class NextRequestHint extends NextRequest {
}

const FLIGHT_PARAMETERS = [
'__rsc__',
'__next_router_state_tree__',
'__next_router_prefetch__',
[RSC],
[NEXT_ROUTER_STATE_TREE],
[NEXT_ROUTER_PREFETCH],
] as const

export async function adapter(params: {
Expand Down Expand Up @@ -71,7 +76,7 @@ export async function adapter(params: {
// Parameters should only be stripped for middleware
if (!isEdgeRendering) {
for (const param of FLIGHT_PARAMETERS) {
requestHeaders.delete(param)
requestHeaders.delete(param.toString().toLowerCase())
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/e2e/app-dir/app/middleware.js
Expand Up @@ -35,8 +35,8 @@ export function middleware(request) {
? 'rewrite'
: 'redirect'

const internal = ['__rsc__', '__next_router_state_tree__']
if (internal.some((name) => request.headers.has(name))) {
const internal = ['RSC', 'Next-Router-State-Tree']
if (internal.some((name) => request.headers.has(name.toLowerCase()))) {
return NextResponse[method](new URL('/internal/failure', request.url))
}

Expand Down
4 changes: 2 additions & 2 deletions test/e2e/app-dir/index.test.ts
Expand Up @@ -84,7 +84,7 @@ describe('app dir', () => {
{},
{
headers: {
__rsc__: '1',
['RSC'.toString()]: '1',
},
}
)
Expand All @@ -98,7 +98,7 @@ describe('app dir', () => {
{},
{
headers: {
__rsc__: '1',
['RSC'.toString()]: '1',
},
}
)
Expand Down
50 changes: 12 additions & 38 deletions test/e2e/app-dir/rsc-basic.test.ts
Expand Up @@ -88,9 +88,6 @@ describe('app dir - rsc basics', () => {
'__nextLocale',
'__nextDefaultLocale',
'__nextIsNotFound',
'__rsc__',
'__next_router_state_tree__',
'__next_router_prefetch__',
]

const hasNextInternalQuery = inlineFlightContents.some((content) =>
Expand All @@ -108,9 +105,9 @@ describe('app dir - rsc basics', () => {
requestsCount++
return request.allHeaders().then((headers) => {
if (
headers.__rsc__ === '1' &&
// Prefetches also include `__rsc__`
headers.__next_router_prefetch__ !== '1'
headers['RSC'.toLowerCase()] === '1' &&
// Prefetches also include `RSC`
headers['Next-Router-Prefetch'.toLowerCase()] !== '1'
) {
hasFlightRequest = true
}
Expand Down Expand Up @@ -185,41 +182,18 @@ describe('app dir - rsc basics', () => {
}
})

it('should refresh correctly with next/link', async () => {
it('should link correctly with next/link without mpa navigation to the page', async () => {
// Select the button which is not hidden but rendered
const selector = '#goto-next-link'
let hasFlightRequest = false
const browser = await webdriver(next.url, '/root', {
beforePageLoad(page) {
page.on('request', (request) => {
return request.allHeaders().then((headers) => {
if (
headers.__rsc__ === '1' &&
headers.__next_router_prefetch__ !== '1'
) {
hasFlightRequest = true
}
})
})
},
})
const browser = await webdriver(next.url, '/root', {})

// wait for hydration
await new Promise((res) => setTimeout(res, 1000))
if (isNextDev) {
expect(hasFlightRequest).toBe(false)
}
await browser.elementByCss(selector).click()
await browser.eval('window.didNotReloadPage = true')
await browser.elementByCss(selector).click().waitForElementByCss('#query')

// wait for re-hydration
if (isNextDev) {
await check(
() => (hasFlightRequest ? 'success' : hasFlightRequest),
'success'
)
}
const refreshText = await browser.elementByCss(selector).text()
expect(refreshText).toBe('next link')
expect(await browser.eval('window.didNotReloadPage')).toBe(true)

const text = await browser.elementByCss('#query').text()
expect(text).toBe('query:0')
})

it('should escape streaming data correctly', async () => {
Expand Down Expand Up @@ -381,7 +355,7 @@ describe('app dir - rsc basics', () => {
{},
{
headers: {
__rsc__: '1',
['RSC'.toString()]: '1',
},
}
).then(async (response) => {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/switchable-runtime/index.test.ts
Expand Up @@ -120,7 +120,7 @@ describe('Switchable runtime', () => {
beforePageLoad(page) {
page.on('request', (request) => {
return request.allHeaders().then((headers) => {
if (headers.__rsc__ === '1') {
if (headers['RSC'.toLowerCase()] === '1') {
flightRequest = request.url()
}
})
Expand Down Expand Up @@ -680,7 +680,7 @@ describe('Switchable runtime', () => {
beforePageLoad(page) {
page.on('request', (request) => {
request.allHeaders().then((headers) => {
if (headers.__rsc__ === '1') {
if (headers['RSC'.toLowerCase()] === '1') {
flightRequest = request.url()
}
})
Expand Down