Skip to content

Commit c1d4562

Browse files
committed
feat: wip ttl support
1 parent 6b61643 commit c1d4562

File tree

4 files changed

+93
-8
lines changed

4 files changed

+93
-8
lines changed

src/helpers/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
129129
// ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
130130
...isrRedirects.map((redirect) => ({
131131
from: `${basePath}${redirect}`,
132-
to: HANDLER_FUNCTION_PATH,
132+
to: process.env.EXPERIMENTAL_ODB_TTL ? ODB_FUNCTION_PATH : HANDLER_FUNCTION_PATH,
133133
status: 200,
134134
force: true,
135135
})),

src/helpers/files.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// @ts-check
1+
/* eslint-disable max-lines */
22
const { cpus } = require('os')
33

44
const { yellowBright } = require('chalk')
5-
const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra')
5+
const { existsSync, readJson, move, cpSync, copy, writeJson, readFile, writeFile } = require('fs-extra')
66
const globby = require('globby')
77
const { outdent } = require('outdent')
88
const pLimit = require('p-limit')
@@ -145,9 +145,61 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
145145
}
146146
}
147147

148+
const patchFile = async ({ file, from, to }) => {
149+
if (!existsSync(file)) {
150+
return
151+
}
152+
const content = await readFile(file, 'utf8')
153+
if (content.includes(to)) {
154+
return
155+
}
156+
const newContent = content.replace(from, to)
157+
await writeFile(`${file}.orig`, content)
158+
await writeFile(file, newContent)
159+
}
160+
161+
const getServerFile = (root) => {
162+
let serverFile
163+
try {
164+
serverFile = require.resolve('next/dist/server/next-server', { paths: [root] })
165+
} catch {
166+
// Ignore
167+
}
168+
if (!serverFile) {
169+
try {
170+
// eslint-disable-next-line node/no-missing-require
171+
serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] })
172+
} catch {
173+
// Ignore
174+
}
175+
}
176+
return serverFile
177+
}
178+
179+
exports.patchNextFiles = async (root) => {
180+
const serverFile = getServerFile(root)
181+
console.log(`Patching ${serverFile}`)
182+
if (serverFile) {
183+
await patchFile({
184+
file: serverFile,
185+
from: `let ssgCacheKey = `,
186+
to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
187+
})
188+
}
189+
}
190+
191+
exports.unpatchNextFiles = async (root) => {
192+
const serverFile = getServerFile(root)
193+
const origFile = `${serverFile}.orig`
194+
if (existsSync(origFile)) {
195+
await move(origFile, serverFile, { overwrite: true })
196+
}
197+
}
198+
148199
exports.movePublicFiles = async ({ appDir, publish }) => {
149200
const publicDir = join(appDir, 'public')
150201
if (existsSync(publicDir)) {
151202
await copy(publicDir, `${publish}/`)
152203
}
153204
}
205+
/* eslint-enable max-lines */

src/index.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { join, relative } = require('path')
33
const { ODB_FUNCTION_NAME } = require('./constants')
44
const { restoreCache, saveCache } = require('./helpers/cache')
55
const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config')
6-
const { moveStaticPages, movePublicFiles } = require('./helpers/files')
6+
const { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } = require('./helpers/files')
77
const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions')
88
const {
99
verifyNetlifyBuildVersion,
@@ -56,6 +56,10 @@ module.exports = {
5656

5757
await movePublicFiles({ appDir, publish })
5858

59+
if (process.env.EXPERIMENTAL_ODB_TTL) {
60+
await patchNextFiles(basePath)
61+
}
62+
5963
if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
6064
console.log(
6165
"The flag 'EXPERIMENTAL_MOVE_STATIC_PAGES' is no longer required, as it is now the default. To disable this behavior, set the env var 'SERVE_STATIC_FILES_FROM_ORIGIN' to 'true'",
@@ -75,10 +79,12 @@ module.exports = {
7579
})
7680
},
7781

78-
async onPostBuild({ netlifyConfig, utils: { cache, functions }, constants: { FUNCTIONS_DIST } }) {
82+
async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) {
7983
await saveCache({ cache, publish: netlifyConfig.build.publish })
8084
await checkForOldFunctions({ functions })
8185
await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`))
86+
const { basePath } = await getNextConfig({ publish: netlifyConfig.build.publish, failBuild })
87+
await unpatchNextFiles(basePath)
8288
},
8389
onEnd() {
8490
logBetaMessage()

src/templates/getHandler.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable max-lines-per-function */
1+
/* eslint-disable max-lines-per-function, max-lines */
22
const { promises, createWriteStream, existsSync } = require('fs')
33
const { Server } = require('http')
44
const { tmpdir } = require('os')
@@ -19,6 +19,10 @@ const makeHandler =
1919
// eslint-disable-next-line node/no-missing-require
2020
require.resolve('./pages.js')
2121
} catch {}
22+
// eslint-disable-next-line no-underscore-dangle
23+
process.env._BYPASS_SSG = 'true'
24+
25+
const ONE_YEAR_IN_SECONDS = 31536000
2226

2327
// We don't want to write ISR files to disk in the lambda environment
2428
conf.experimental.isrFlushToDisk = false
@@ -114,6 +118,22 @@ const makeHandler =
114118
const bridge = new Bridge(server)
115119
bridge.listen()
116120

121+
const getMaxAge = (header) => {
122+
const parts = header.split(',')
123+
let maxAge
124+
for (const part of parts) {
125+
const [key, value] = part.split('=')
126+
if (key?.trim() === 's-max-age') {
127+
maxAge = value?.trim()
128+
}
129+
}
130+
if (maxAge) {
131+
const result = Number.parseInt(maxAge)
132+
return Number.isNaN(result) ? 0 : result
133+
}
134+
return 0
135+
}
136+
117137
return async (event, context) => {
118138
// Ensure that paths are encoded - but don't double-encode them
119139
event.path = new URL(event.path, event.rawUrl).pathname
@@ -147,7 +167,14 @@ const makeHandler =
147167
// Sending SWR headers causes undefined behaviour with the Netlify CDN
148168
const cacheHeader = multiValueHeaders['cache-control']?.[0]
149169
if (cacheHeader?.includes('stale-while-revalidate')) {
150-
console.log({ cacheHeader })
170+
if (mode === 'odb' && process.env.EXPERIMENTAL_ODB_TTL) {
171+
mode = 'isr'
172+
const ttl = getMaxAge(cacheHeader)
173+
// Long-expiry TTL is basically no TTL
174+
if (ttl > 0 && ttl < ONE_YEAR_IN_SECONDS) {
175+
result.ttl = Math.min(ttl, 60)
176+
}
177+
}
151178
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate']
152179
}
153180
multiValueHeaders['x-render-mode'] = [mode]
@@ -186,4 +213,4 @@ exports.handler = ${
186213
`
187214

188215
module.exports = getHandler
189-
/* eslint-enable max-lines-per-function */
216+
/* eslint-enable max-lines-per-function, max-lines */

0 commit comments

Comments
 (0)