/
app-page.ts
163 lines (137 loc) · 4.41 KB
/
app-page.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import type { ExportPageResult } from '../types'
import type { AppPageRender } from '../../server/app-render/app-render'
import type { RenderOpts } from '../../server/app-render/types'
import type { OutgoingHttpHeaders } from 'http'
import type { NextParsedUrlQuery } from '../../server/request-meta'
import fs from 'fs/promises'
import { MockedRequest, MockedResponse } from '../../server/lib/mock-request'
import {
RSC,
NEXT_URL,
NEXT_ROUTER_PREFETCH,
} from '../../client/components/app-router-headers'
import { isDynamicUsageError } from '../helpers/is-dynamic-usage-error'
import { NEXT_CACHE_TAGS_HEADER } from '../../lib/constants'
import { hasNextSupport } from '../../telemetry/ci-info'
/**
* Lazily loads and runs the app page render function.
*/
const render: AppPageRender = (...args) => {
return require('../../server/future/route-modules/app-page/module.compiled').renderToHTMLOrFlight(
...args
)
}
export async function generatePrefetchRsc(
req: MockedRequest,
path: string,
res: MockedResponse,
pathname: string,
htmlFilepath: string,
renderOpts: RenderOpts
) {
req.headers[RSC.toLowerCase()] = '1'
req.headers[NEXT_URL.toLowerCase()] = path
req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()] = '1'
renderOpts.supportsDynamicHTML = true
renderOpts.isPrefetch = true
delete renderOpts.isRevalidate
const prefetchRenderResult = await render(req, res, pathname, {}, renderOpts)
prefetchRenderResult.pipe(res)
await res.hasStreamed
const prefetchRscData = Buffer.concat(res.buffers)
if ((renderOpts as any).store.staticPrefetchBailout) return
await fs.writeFile(
htmlFilepath.replace(/\.html$/, '.prefetch.rsc'),
prefetchRscData
)
}
export async function exportAppPage(
req: MockedRequest,
res: MockedResponse,
page: string,
path: string,
pathname: string,
query: NextParsedUrlQuery,
renderOpts: RenderOpts,
htmlFilepath: string,
debugOutput: boolean,
isDynamicError: boolean,
isAppPrefetch: boolean
): Promise<ExportPageResult> {
// If the page is `/_not-found`, then we should update the page to be `/404`.
if (page === '/_not-found') {
pathname = '/404'
}
try {
if (isAppPrefetch) {
await generatePrefetchRsc(
req,
path,
res,
pathname,
htmlFilepath,
renderOpts
)
return { fromBuildExportRevalidate: 0 }
}
const result = await render(req, res, pathname, query, renderOpts)
const html = result.toUnchunkedString()
const { metadata } = result
const flightData = metadata.pageData
const revalidate = metadata.revalidate
if (revalidate === 0) {
if (isDynamicError) {
throw new Error(
`Page with dynamic = "error" encountered dynamic data method on ${path}.`
)
}
if (!(renderOpts as any).store.staticPrefetchBailout) {
await generatePrefetchRsc(
req,
path,
res,
pathname,
htmlFilepath,
renderOpts
)
}
const { staticBailoutInfo = {} } = metadata
if (revalidate === 0 && debugOutput && staticBailoutInfo?.description) {
const err = new Error(
`Static generation failed due to dynamic usage on ${path}, reason: ${staticBailoutInfo.description}`
)
// Update the stack if it was provided via the bailout info.
const { stack } = staticBailoutInfo
if (stack) {
err.stack = err.message + stack.substring(stack.indexOf('\n'))
}
console.warn(err)
}
return { fromBuildExportRevalidate: 0 }
}
let headers: OutgoingHttpHeaders | undefined
if (metadata.fetchTags) {
headers = { [NEXT_CACHE_TAGS_HEADER]: metadata.fetchTags }
}
// Writing static HTML to a file.
await fs.writeFile(htmlFilepath, html ?? '', 'utf8')
// Writing the request metadata to a file.
const meta = { headers }
await fs.writeFile(
htmlFilepath.replace(/\.html$/, '.meta'),
JSON.stringify(meta)
)
// Writing the RSC payload to a file.
await fs.writeFile(htmlFilepath.replace(/\.html$/, '.rsc'), flightData)
return {
fromBuildExportRevalidate: revalidate,
// Only include the metadata if the environment has next support.
fromBuildExportMeta: hasNextSupport ? meta : undefined,
}
} catch (err: any) {
if (!isDynamicUsageError(err)) {
throw err
}
return { fromBuildExportRevalidate: 0 }
}
}