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

Forward headers from React to static output and dynamic render #58162

Merged
merged 3 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/next/src/export/routes/app-page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ExportRouteResult, FileWriter } from '../types'
import type { RenderOpts } from '../../server/app-render/types'
import type { OutgoingHttpHeaders } from 'http'
import type { NextParsedUrlQuery } from '../../server/request-meta'
import type { RouteMetadata } from './types'

Expand Down Expand Up @@ -184,9 +183,10 @@ export async function exportAppPage(
)
}

let headers: OutgoingHttpHeaders | undefined
const headers = { ...metadata.extraHeaders }

if (fetchTags) {
headers = { [NEXT_CACHE_TAGS_HEADER]: fetchTags }
headers[NEXT_CACHE_TAGS_HEADER] = fetchTags
}

// Writing static HTML to a file.
Expand Down
12 changes: 12 additions & 0 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -736,9 +736,21 @@ async function renderToHTMLOrFlightImpl(
hasPostponed,
})

function onHeaders(headers: Headers): void {
// Copy headers created by React into the response object.
headers.forEach((value: string, key: string) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will only actually be the Link: header so far but we could expand it later.

res.appendHeader(key, value)
if (!extraRenderResultMeta.extraHeaders) {
extraRenderResultMeta.extraHeaders = {}
}
extraRenderResultMeta.extraHeaders[key] = value
})
}

try {
const renderStream = await renderer.render(content, {
onError: htmlRendererErrorHandler,
onHeaders: onHeaders,
nonce,
bootstrapScripts: [bootstrapScript],
formState,
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/server/app-render/static/static-renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type StreamOptions = {
onError?: (error: Error) => void
onHeaders?: (headers: Headers) => void
nonce?: string
bootstrapScripts?: {
src: string
Expand Down Expand Up @@ -38,6 +39,10 @@ class StaticResumeRenderer implements Renderer {
constructor(private readonly postponed: object) {}

public async render(children: JSX.Element, streamOptions: StreamOptions) {
// TODO: Refactor StreamOptions because not all options apply to all React
// functions so this factoring of trying to reuse a single render() doesn't
// make sense. This is passing multiple invalid options that React should
// error for.
const stream = await this.resume(children, this.postponed, streamOptions)

return { stream }
Expand Down
24 changes: 21 additions & 3 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2373,10 +2373,11 @@ export default abstract class Server<ServerOptions extends Options = Options> {

// Add any fetch tags that were on the page to the response headers.
const cacheTags = metadata.fetchTags

headers = { ...metadata.extraHeaders }

if (cacheTags) {
headers = {
[NEXT_CACHE_TAGS_HEADER]: cacheTags,
}
headers[NEXT_CACHE_TAGS_HEADER] = cacheTags
}

// Pull any fetch metrics from the render onto the request.
Expand Down Expand Up @@ -2769,6 +2770,23 @@ export default abstract class Server<ServerOptions extends Options = Options> {
)
}

if (cachedData.headers) {
const resHeaders = cachedData.headers
for (const key of Object.keys(resHeaders)) {
if (key === NEXT_CACHE_TAGS_HEADER) {
// Not sure if needs to be special.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

continue
}
let v = resHeaders[key]
if (typeof v !== 'undefined') {
if (typeof v === 'number') {
v = v.toString()
}
res.setHeader(key, v)
}
Comment on lines +2781 to +2786
Copy link
Member

Choose a reason for hiding this comment

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

Personally prefer to perform an early continue/return rather than add more nesting, but not required!

}
}

if (
this.minimalMode &&
isSSG &&
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/server/render-result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ServerResponse } from 'http'
import type { OutgoingHttpHeaders, ServerResponse } from 'http'
import type { StaticGenerationStore } from '../client/components/static-generation-async-storage.external'
import type { Revalidate } from './lib/revalidate'

Expand All @@ -23,6 +23,7 @@ export type RenderResultMetadata = {
isRedirect?: boolean
fetchMetrics?: StaticGenerationStore['fetchMetrics']
fetchTags?: string
extraHeaders?: OutgoingHttpHeaders
waitUntil?: Promise<any>
postponed?: string
}
Expand Down