Skip to content

Commit e1d6c4e

Browse files
committed
fix: correct handling of CDN files
1 parent 62c24a7 commit e1d6c4e

File tree

4 files changed

+55
-10
lines changed

4 files changed

+55
-10
lines changed

demos/default/next.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ module.exports = {
3131
destination: '/:path*',
3232
},
3333
],
34+
afterFiles: [{
35+
source: '/rewriteToStatic',
36+
destination: '/getStaticProps/1',
37+
}]
3438
}
3539
},
3640
// Redirects allow you to redirect an incoming request path to a different destination path.

demos/default/pages/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ const Index = ({ shows }) => {
167167
<a>Rewrite (should display image)</a>
168168
</Link>
169169
</li>
170+
<li>
171+
<Link href="/rewriteToStatic">
172+
<a>Rewrite to static (should show getStaticProps/1)</a>
173+
</Link>
174+
</li>
170175
<li>
171176
<Link href="/middle">
172177
<a>Middleware</a>

src/helpers/files.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const { cpus } = require('os')
33

44
const { yellowBright } = require('chalk')
5-
const { existsSync, readJson, move, cpSync, copy, writeJson, readFile, writeFile } = require('fs-extra')
5+
const { existsSync, readJson, move, copy, writeJson, readFile, writeFile, ensureDir } = require('fs-extra')
66
const globby = require('globby')
77
const { outdent } = require('outdent')
88
const pLimit = require('p-limit')
@@ -62,7 +62,9 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
6262
console.log('Moving static page files to serve from CDN...')
6363
const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless')
6464
const root = join(outputDir, 'pages')
65-
65+
const buildId = await readFile(join(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8')
66+
const dataDir = join('_next', 'data', buildId)
67+
await ensureDir(dataDir)
6668
// Load the middleware manifest so we can check if a file matches it before moving
6769
let middleware
6870
const manifestPath = join(outputDir, 'middleware-manifest.json')
@@ -88,10 +90,17 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
8890
})
8991

9092
const files = []
93+
const filesManifest = {}
9194
const moveFile = async (file) => {
95+
const isData = file.endsWith('.json')
9296
const source = join(root, file)
97+
const target = isData ? join(dataDir, file) : file
98+
9399
files.push(file)
94-
const dest = join(netlifyConfig.build.publish, file)
100+
filesManifest[file] = target
101+
102+
const dest = join(netlifyConfig.build.publish, target)
103+
95104
try {
96105
await move(source, dest)
97106
} catch (error) {
@@ -208,7 +217,7 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
208217
}
209218

210219
// Write the manifest for use in the serverless functions
211-
await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files)
220+
await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest))
212221

213222
if (i18n?.defaultLocale) {
214223
// Copy the default locale into the root

src/templates/getHandler.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines-per-function */
12
const { promises, existsSync } = require('fs')
23
const { Server } = require('http')
34
const { tmpdir } = require('os')
@@ -34,12 +35,14 @@ const makeHandler =
3435
// In most cases these are served from the CDN, but for rewrites Next may try to read them
3536
// from disk. We need to intercept these and load them from the CDN instead
3637
// Sadly the only way to do this is to monkey-patch fs.promises. Yeah, I know.
37-
const staticFiles = new Set(staticManifest)
38-
38+
const staticFiles = new Map(staticManifest)
39+
const downloadPromises = new Map()
40+
const statsCache = new Map()
3941
// Yes, you can cache stuff locally in a Lambda
4042
const cacheDir = path.join(tmpdir(), 'next-static-cache')
4143
// Grab the real fs.promises.readFile...
4244
const readfileOrig = promises.readFile
45+
const statsOrig = promises.stat
4346
// ...then money-patch it to see if it's requesting a CDN file
4447
promises.readFile = async (file, options) => {
4548
// We only care about page files
@@ -51,13 +54,24 @@ const makeHandler =
5154
if (staticFiles.has(filePath) && !existsSync(file)) {
5255
// This name is safe to use, because it's one that was already created by Next
5356
const cacheFile = path.join(cacheDir, filePath)
54-
// Have we already cached it? We ignore the cache if running locally to avoid staleness
57+
const url = `${base}/${staticFiles.get(filePath)}`
58+
59+
// If it's already downloading we can wait for it to finish
60+
if (downloadPromises.has(url)) {
61+
await downloadPromises.get(url)
62+
}
63+
// Have we already cached it? We download every time if running locally to avoid staleness
5564
if ((!existsSync(cacheFile) || process.env.NETLIFY_DEV) && base) {
5665
await promises.mkdir(path.dirname(cacheFile), { recursive: true })
5766

58-
// Append the path to our host and we can load it like a regular page
59-
const url = `${base}/${filePath}`
60-
await downloadFile(url, cacheFile)
67+
try {
68+
// Append the path to our host and we can load it like a regular page
69+
const downloadPromise = downloadFile(url, cacheFile)
70+
downloadPromises.set(url, downloadPromise)
71+
await downloadPromise
72+
} finally {
73+
downloadPromises.delete(url)
74+
}
6175
}
6276
// Return the cache file
6377
return readfileOrig(cacheFile, options)
@@ -66,6 +80,18 @@ const makeHandler =
6680

6781
return readfileOrig(file, options)
6882
}
83+
84+
promises.stat = async (file, options) => {
85+
// We only care about page files
86+
if (file.startsWith(pageRoot)) {
87+
// We only want the part after `pages/`
88+
const cacheFile = path.join(cacheDir, file.slice(pageRoot.length + 1))
89+
if (existsSync(cacheFile)) {
90+
return statsOrig(cacheFile, options)
91+
}
92+
}
93+
return statsOrig(file, options)
94+
}
6995
}
7096
let NextServer
7197
try {
@@ -183,3 +209,4 @@ exports.handler = ${
183209
`
184210

185211
module.exports = getHandler
212+
/* eslint-enable max-lines-per-function */

0 commit comments

Comments
 (0)