-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathserver.ts
159 lines (135 loc) · 6.04 KB
/
server.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
// eslint-disable-next-line n/no-deprecated-api -- this is what Next.js uses as well
import { parse } from 'url'
import type { PrerenderManifest } from 'next/dist/build'
import type { BaseNextResponse } from 'next/dist/server/base-http'
import type { NodeRequestHandler, Options } from 'next/dist/server/next-server'
import {
netlifyApiFetch,
NextServerType,
normalizeRoute,
localizeRoute,
localizeDataRoute,
unlocalizeRoute,
} from './handlerUtils'
interface NetlifyConfig {
revalidateToken?: string
}
const getNetlifyNextServer = (NextServer: NextServerType) => {
class NetlifyNextServer extends NextServer {
private netlifyConfig: NetlifyConfig
private netlifyPrerenderManifest: PrerenderManifest
public constructor(options: Options, netlifyConfig: NetlifyConfig) {
super(options)
this.netlifyConfig = netlifyConfig
// copy the prerender manifest so it doesn't get mutated by Next.js
const manifest = this.getPrerenderManifest()
this.netlifyPrerenderManifest = {
...manifest,
routes: { ...manifest.routes },
dynamicRoutes: { ...manifest.dynamicRoutes },
}
}
public getRequestHandler(): NodeRequestHandler {
const handler = super.getRequestHandler()
return async (req, res, parsedUrl) => {
if (!parsedUrl && typeof req?.headers?.['x-middleware-rewrite'] === 'string') {
parsedUrl = parse(req.headers['x-middleware-rewrite'], true)
}
// preserve the URL before Next.js mutates it for i18n
const { url, headers } = req
// conditionally use the prebundled React module
this.netlifyPrebundleReact(url)
// intercept on-demand revalidation requests and handle with the Netlify API
if (headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
// handle on-demand revalidation by purging the ODB cache
await this.netlifyRevalidate(url)
res = res as unknown as BaseNextResponse
res.statusCode = 200
res.setHeader('x-nextjs-cache', 'REVALIDATED')
res.send()
return
}
// force Next to revalidate all requests so that we always have fresh content
// for our ODBs and middleware is disabled at the origin
// but ignore in preview mode (prerender_bypass is set to true in preview mode)
// because otherwise revalidate will override preview mode
if (!headers.cookie?.includes('__prerender_bypass')) {
// this header controls whether Next.js will revalidate the page
// and needs to be set to the preview mode id to enable it
headers['x-prerender-revalidate'] = this.renderOpts.previewProps.previewModeId
}
return handler(req, res, parsedUrl)
}
}
// doing what they do in https://github.com/vercel/vercel/blob/1663db7ca34d3dd99b57994f801fb30b72fbd2f3/packages/next/src/server-build.ts#L576-L580
private netlifyPrebundleReact(path: string) {
const routesManifest = this.getRoutesManifest?.()
const appPathsRoutes = this.getAppPathRoutes?.()
const routes = routesManifest && [...routesManifest.staticRoutes, ...routesManifest.dynamicRoutes]
const matchedRoute = routes?.find((route) => new RegExp(route.regex).test(new URL(path, 'http://n').pathname))
const isAppRoute = appPathsRoutes && matchedRoute ? appPathsRoutes[matchedRoute.page] : false
if (isAppRoute) {
// app routes should use prebundled React
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = this.nextConfig.experimental?.serverActions
? 'experimental'
: 'next'
return
}
// pages routes should use use node_modules React
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = ''
}
private async netlifyRevalidate(route: string) {
try {
// call netlify API to revalidate the path
const result = await netlifyApiFetch<{ ok: boolean; code: number; message: string }>({
endpoint: `sites/${process.env.SITE_ID}/refresh_on_demand_builders`,
payload: {
paths: this.getNetlifyPathsForRoute(route),
domain: this.hostname,
},
token: this.netlifyConfig.revalidateToken,
method: 'POST',
})
if (!result.ok) {
throw new Error(result.message)
}
} catch (error) {
console.log(`Error revalidating ${route}:`, error.message)
throw error
}
}
private getNetlifyPathsForRoute(route: string): string[] {
const { i18n } = this.nextConfig
const { routes, dynamicRoutes } = this.netlifyPrerenderManifest
// matches static routes
const normalizedRoute = normalizeRoute(i18n ? localizeRoute(route, i18n) : route)
if (normalizedRoute in routes) {
const { dataRoute } = routes[normalizedRoute]
const normalizedDataRoute = i18n ? localizeDataRoute(dataRoute, normalizedRoute) : dataRoute
return [route, normalizedDataRoute]
}
// matches dynamic routes
const unlocalizedRoute = i18n ? unlocalizeRoute(normalizedRoute, i18n) : normalizedRoute
for (const dynamicRoute in dynamicRoutes) {
const { dataRoute, routeRegex } = dynamicRoutes[dynamicRoute]
const matches = unlocalizedRoute.match(routeRegex)
if (matches?.length > 0) {
// remove the first match, which is the full route
matches.shift()
// replace the dynamic segments with the actual values
const interpolatedDataRoute = dataRoute.replace(/\[(.*?)]/g, () => matches.shift())
const normalizedDataRoute = i18n
? localizeDataRoute(interpolatedDataRoute, normalizedRoute)
: interpolatedDataRoute
return [route, normalizedDataRoute]
}
}
throw new Error(`not an ISR route`)
}
}
return NetlifyNextServer
}
export type NetlifyNextServerType = ReturnType<typeof getNetlifyNextServer>
export { getNetlifyNextServer, NetlifyConfig }