@netlify/plugin-nextjs 4.28.3 → 4.28.4-appdir.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadMiddlewareManifest = void 0;
6
+ exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadAppPathRoutesManifest = exports.loadMiddlewareManifest = void 0;
7
7
  /* eslint-disable max-lines */
8
8
  const fs_1 = require("fs");
9
9
  const path_1 = require("path");
@@ -13,14 +13,15 @@ const fs_extra_1 = require("fs-extra");
13
13
  const outdent_1 = require("outdent");
14
14
  const config_1 = require("./config");
15
15
  const matchers_1 = require("./matchers");
16
- const loadMiddlewareManifest = (netlifyConfig) => {
17
- const middlewarePath = (0, path_1.resolve)(netlifyConfig.build.publish, 'server', 'middleware-manifest.json');
18
- if (!(0, fs_1.existsSync)(middlewarePath)) {
19
- return null;
16
+ const maybeLoadJson = (path) => {
17
+ if ((0, fs_1.existsSync)(path)) {
18
+ return (0, fs_extra_1.readJson)(path);
20
19
  }
21
- return (0, fs_extra_1.readJson)(middlewarePath);
22
20
  };
21
+ const loadMiddlewareManifest = (netlifyConfig) => maybeLoadJson((0, path_1.resolve)(netlifyConfig.build.publish, 'server', 'middleware-manifest.json'));
23
22
  exports.loadMiddlewareManifest = loadMiddlewareManifest;
23
+ const loadAppPathRoutesManifest = (netlifyConfig) => maybeLoadJson((0, path_1.resolve)(netlifyConfig.build.publish, 'app-path-routes-manifest.json'));
24
+ exports.loadAppPathRoutesManifest = loadAppPathRoutesManifest;
24
25
  /**
25
26
  * Convert the Next middleware name into a valid Edge Function name
26
27
  */
@@ -95,7 +96,7 @@ const getMiddlewareBundle = async ({ edgeFunctionDefinition, netlifyConfig, }) =
95
96
  };
96
97
  const getEdgeTemplatePath = (file) => (0, path_1.join)(__dirname, '..', '..', 'src', 'templates', 'edge', file);
97
98
  const copyEdgeSourceFile = ({ file, target, edgeFunctionDir, }) => fs_1.promises.copyFile(getEdgeTemplatePath(file), (0, path_1.join)(edgeFunctionDir, target !== null && target !== void 0 ? target : file));
98
- const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, nextConfig, }) => {
99
+ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, pageRegexMap, appPathRoutesManifest = {}, nextConfig, }) => {
99
100
  const name = sanitizeName(edgeFunctionDefinition.name);
100
101
  const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, name);
101
102
  const bundle = await getMiddlewareBundle({
@@ -123,6 +124,14 @@ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, net
123
124
  else {
124
125
  matchers.push(...edgeFunctionDefinition.matchers);
125
126
  }
127
+ // If the EF matches a page, it's an app dir page so needs a matcher too
128
+ // The object will be empty if appDir isn't enabled in the Next config
129
+ if (pageRegexMap && edgeFunctionDefinition.page in appPathRoutesManifest) {
130
+ const regexp = pageRegexMap.get(appPathRoutesManifest[edgeFunctionDefinition.page]);
131
+ if (regexp) {
132
+ matchers.push({ regexp });
133
+ }
134
+ }
126
135
  await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionDir, 'matchers.json'), matchers);
127
136
  // We add a defintion for each matching path
128
137
  return matchers.map((matcher) => {
@@ -155,7 +164,7 @@ exports.writeDevEdgeFunction = writeDevEdgeFunction;
155
164
  /**
156
165
  * Writes Edge Functions for the Next middleware
157
166
  */
158
- const writeEdgeFunctions = async (netlifyConfig) => {
167
+ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
159
168
  const manifest = {
160
169
  functions: [],
161
170
  version: 1,
@@ -202,12 +211,20 @@ const writeEdgeFunctions = async (netlifyConfig) => {
202
211
  // Older versions of the manifest format don't have the functions field
203
212
  // No, the version field was not incremented
204
213
  if (typeof middlewareManifest.functions === 'object') {
214
+ // When using the app dir, we also need to check if the EF matches a page
215
+ const appPathRoutesManifest = await (0, exports.loadAppPathRoutesManifest)(netlifyConfig);
216
+ const pageRegexMap = new Map([...(routesManifest.dynamicRoutes || []), ...(routesManifest.staticRoutes || [])].map((route) => [
217
+ route.page,
218
+ route.regex,
219
+ ]));
205
220
  for (const edgeFunctionDefinition of Object.values(middlewareManifest.functions)) {
206
221
  usesEdge = true;
207
222
  const functionDefinitions = await writeEdgeFunction({
208
223
  edgeFunctionDefinition,
209
224
  edgeFunctionRoot,
210
225
  netlifyConfig,
226
+ pageRegexMap,
227
+ appPathRoutesManifest,
211
228
  nextConfig,
212
229
  });
213
230
  manifest.functions.push(...functionDefinitions);
@@ -70,7 +70,6 @@ exports.getMiddleware = getMiddleware;
70
70
  const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
71
71
  console.log('Moving static page files to serve from CDN...');
72
72
  const outputDir = (0, pathe_1.join)(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
73
- const root = (0, pathe_1.join)(outputDir, 'pages');
74
73
  const buildId = (0, fs_extra_1.readFileSync)((0, pathe_1.join)(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
75
74
  const dataDir = (0, pathe_1.join)('_next', 'data', buildId);
76
75
  await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(netlifyConfig.build.publish, dataDir));
@@ -92,14 +91,17 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
92
91
  }
93
92
  }
94
93
  });
95
- const files = [];
94
+ let fileCount = 0;
96
95
  const filesManifest = {};
97
96
  const moveFile = async (file) => {
97
+ // Strip the initial 'app' or 'pages' directory from the output path
98
+ const pathname = file.split('/').slice(1).join('/');
99
+ // .rsc data files go next to the html file
98
100
  const isData = file.endsWith('.json');
99
- const source = (0, pathe_1.join)(root, file);
100
- const targetFile = isData ? (0, pathe_1.join)(dataDir, file) : file;
101
+ const source = (0, pathe_1.join)(outputDir, file);
102
+ const targetFile = isData ? (0, pathe_1.join)(dataDir, pathname) : pathname;
101
103
  const targetPath = basePath ? (0, pathe_1.join)(basePath, targetFile) : targetFile;
102
- files.push(file);
104
+ fileCount += 1;
103
105
  filesManifest[file] = targetPath;
104
106
  const dest = (0, pathe_1.join)(netlifyConfig.build.publish, targetPath);
105
107
  try {
@@ -110,8 +112,8 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
110
112
  }
111
113
  };
112
114
  // Move all static files, except error documents and nft manifests
113
- const pages = await (0, globby_1.default)(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
114
- cwd: root,
115
+ const pages = await (0, globby_1.default)(['{app,pages}/**/*.{html,json,rsc}', '!**/(500|404|*.js.nft).{html,json}'], {
116
+ cwd: outputDir,
115
117
  dot: true,
116
118
  });
117
119
  const matchingMiddleware = new Set();
@@ -149,7 +151,7 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
149
151
  return limit(moveFile, filePath);
150
152
  });
151
153
  await Promise.all(promises);
152
- console.log(`Moved ${files.length} files`);
154
+ console.log(`Moved ${fileCount} files`);
153
155
  if (matchedPages.size !== 0) {
154
156
  console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
155
157
  Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead.
@@ -20,10 +20,13 @@ const utils_1 = require("./utils");
20
20
  const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
21
21
  const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
22
22
  const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
23
- console.log({ functionsDir });
24
23
  const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
25
24
  const publishDir = (0, pathe_1.relative)(functionDir, publish);
26
25
  for (const { route, config, compiled } of apiRoutes) {
26
+ // Don't write a lambda if the runtime is edge
27
+ if (config.runtime === 'experimental-edge') {
28
+ continue;
29
+ }
27
30
  const apiHandlerSource = await (0, getApiHandler_1.getApiHandler)({
28
31
  page: route,
29
32
  config,
package/lib/index.js CHANGED
@@ -44,7 +44,7 @@ const plugin = {
44
44
  }
45
45
  const { publish } = netlifyConfig.build;
46
46
  (0, verification_1.checkNextSiteHasBuilt)({ publish, failBuild });
47
- const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental } = await (0, config_1.getNextConfig)({
47
+ const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental, routesManifest } = await (0, config_1.getNextConfig)({
48
48
  publish,
49
49
  failBuild,
50
50
  });
@@ -118,7 +118,7 @@ const plugin = {
118
118
  buildId,
119
119
  apiRoutes,
120
120
  });
121
- await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
121
+ await (0, edge_1.writeEdgeFunctions)({ netlifyConfig, routesManifest });
122
122
  },
123
123
  async onPostBuild({ netlifyConfig: { build: { publish }, redirects, headers, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
124
124
  await (0, cache_1.saveCache)({ cache, publish });
@@ -105,7 +105,7 @@ const getApiHandler = ({ page, config, publishDir = '../../../.next', appDir = '
105
105
  const { config } = require("${publishDir}/required-server-files.json")
106
106
  let staticManifest
107
107
  const path = require("path");
108
- const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "serverless", "pages"));
108
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
109
109
  const handler = (${makeHandler.toString()})(config, "${appDir}", pageRoot, ${JSON.stringify(page)})
110
110
  exports.handler = ${config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */ ? `schedule(${JSON.stringify(config.schedule)}, handler);` : 'handler'}
111
111
  `;
@@ -15,6 +15,7 @@ const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = requ
15
15
  // eslint-disable-next-line max-params
16
16
  const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
17
17
  var _a;
18
+ console.log('makeHandler', process.env);
18
19
  // Change working directory into the site root, unless using Nx, which moves the
19
20
  // dist directory and handles this itself
20
21
  const dir = path.resolve(__dirname, app);
@@ -37,9 +38,7 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
37
38
  for (const [key, value] of Object.entries(conf.env)) {
38
39
  process.env[key] = String(value);
39
40
  }
40
- // Set during the request as it needs the host header. Hoisted so we can define the function once
41
- let base;
42
- augmentFsModule({ promises, staticManifest, pageRoot, getBase: () => base });
41
+ augmentFsModule({ promises, staticManifest, pageRoot });
43
42
  // We memoize this because it can be shared between requests, but don't instantiate it until
44
43
  // the first request because we need the host and port.
45
44
  let bridge;
@@ -49,9 +48,6 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
49
48
  }
50
49
  const url = new URL(event.rawUrl);
51
50
  const port = Number.parseInt(url.port) || 80;
52
- const { host } = event.headers;
53
- const protocol = event.headers['x-forwarded-proto'] || 'http';
54
- base = `${protocol}://${host}`;
55
51
  const NextServer = getNextServer();
56
52
  const nextServer = new NextServer({
57
53
  conf,
@@ -76,6 +72,8 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
76
72
  };
77
73
  return async function handler(event, context) {
78
74
  var _a, _b, _c;
75
+ const baseUrl = process.env.CONTEXT === 'production' ? process.env.URL : process.env.DEPLOY_PRIME_URL;
76
+ console.log('Using baseUrl', baseUrl, process.env);
79
77
  let requestMode = mode;
80
78
  // Ensure that paths are encoded - but don't double-encode them
81
79
  event.path = new URL(event.rawUrl).pathname;
@@ -142,7 +140,7 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
142
140
  staticManifest = require("${publishDir}/static-manifest.json")
143
141
  } catch {}
144
142
  const path = require("path");
145
- const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
143
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
146
144
  exports.handler = ${isODB
147
145
  ? `builder((${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
148
146
  : `(${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
@@ -5,26 +5,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getSinglePageResolver = exports.getPageResolver = void 0;
7
7
  const path_1 = require("path");
8
+ const fs_extra_1 = require("fs-extra");
9
+ const globby_1 = __importDefault(require("globby"));
8
10
  const outdent_1 = require("outdent");
9
11
  const pathe_1 = require("pathe");
10
12
  const slash_1 = __importDefault(require("slash"));
11
- const tiny_glob_1 = __importDefault(require("tiny-glob"));
12
13
  const constants_1 = require("../constants");
13
14
  const files_1 = require("../helpers/files");
14
15
  // Generate a file full of require.resolve() calls for all the pages in the
15
16
  // build. This is used by the nft bundler to find all the pages.
16
17
  const getPageResolver = async ({ publish, target }) => {
17
- const functionDir = path_1.posix.resolve(path_1.posix.join('.netlify', 'functions', constants_1.HANDLER_FUNCTION_NAME));
18
- const root = path_1.posix.resolve((0, slash_1.default)(publish), target === 'server' ? 'server' : 'serverless', 'pages');
19
- const pages = await (0, tiny_glob_1.default)('**/*.js', {
18
+ const functionDir = (0, path_1.resolve)((0, path_1.join)('.netlify', 'functions', constants_1.HANDLER_FUNCTION_NAME));
19
+ const root = (0, path_1.resolve)((0, slash_1.default)(publish), target === 'server' ? 'server' : 'serverless');
20
+ const pages = await (0, globby_1.default)('{pages,app}/**/*.js.nft.json', {
20
21
  cwd: root,
21
22
  dot: true,
22
23
  });
23
- const pageFiles = pages
24
- .map((page) => `require.resolve('${path_1.posix.relative(functionDir, path_1.posix.join(root, (0, slash_1.default)(page)))}')`)
25
- .sort();
24
+ const dependencies = await Promise.all(pages.map(async (page) => {
25
+ const dir = (0, path_1.dirname)(page);
26
+ const { files } = await (0, fs_extra_1.readJSON)((0, path_1.join)(root, page));
27
+ return files === null || files === void 0 ? void 0 : files.map((file) => (0, path_1.resolve)(root, dir, file));
28
+ }));
29
+ const deduped = [...new Set(dependencies.flat())];
30
+ const pageFiles = deduped.map((file) => `require.resolve('${(0, pathe_1.relative)(functionDir, file)}')`).sort();
26
31
  return (0, outdent_1.outdent) `
27
- // This file is purely to allow nft to know about these pages. It should be temporary.
32
+ // This file is purely to allow nft to know about these pages.
28
33
  exports.resolvePages = () => {
29
34
  try {
30
35
  ${pageFiles.join('\n ')}
@@ -75,7 +75,7 @@ exports.getMultiValueHeaders = getMultiValueHeaders;
75
75
  /**
76
76
  * Monkey-patch the fs module to download missing files from the CDN
77
77
  */
78
- const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
78
+ const augmentFsModule = ({ promises, staticManifest, pageRoot, }) => {
79
79
  // Only do this if we have some static files moved to the CDN
80
80
  if (staticManifest.length === 0) {
81
81
  return;
@@ -93,22 +93,24 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
93
93
  const statsOrig = promises.stat;
94
94
  // ...then money-patch it to see if it's requesting a CDN file
95
95
  promises.readFile = (async (file, options) => {
96
- const base = getBase();
96
+ // In production use the public URL (e.g. https://example.com). Otherwise use the deploy URL, e.g. https://deploy-preview-123--example.netlify.app
97
+ const baseUrl = process.env.CONTEXT === 'production' ? process.env.URL : process.env.DEPLOY_PRIME_URL;
98
+ console.log('Using baseUrl', baseUrl);
97
99
  // We only care about page files
98
100
  if (file.startsWith(pageRoot)) {
99
- // We only want the part after `pages/`
101
+ // We only want the part after `.next/server/`
100
102
  const filePath = file.slice(pageRoot.length + 1);
101
103
  // Is it in the CDN and not local?
102
104
  if (staticFiles.has(filePath) && !(0, fs_1.existsSync)(file)) {
103
105
  // This name is safe to use, because it's one that was already created by Next
104
106
  const cacheFile = path_1.default.join(cacheDir, filePath);
105
- const url = `${base}/${staticFiles.get(filePath)}`;
107
+ const url = `${baseUrl}/${staticFiles.get(filePath)}`;
106
108
  // If it's already downloading we can wait for it to finish
107
109
  if (downloadPromises.has(url)) {
108
110
  await downloadPromises.get(url);
109
111
  }
110
112
  // Have we already cached it? We download every time if running locally to avoid staleness
111
- if ((!(0, fs_1.existsSync)(cacheFile) || process.env.NETLIFY_DEV) && base) {
113
+ if ((!(0, fs_1.existsSync)(cacheFile) || process.env.NETLIFY_DEV) && baseUrl) {
112
114
  await promises.mkdir(path_1.default.dirname(cacheFile), { recursive: true });
113
115
  try {
114
116
  // Append the path to our host and we can load it like a regular page
@@ -129,7 +131,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
129
131
  promises.stat = ((file, options) => {
130
132
  // We only care about page files
131
133
  if (file.startsWith(pageRoot)) {
132
- // We only want the part after `pages/`
134
+ // We only want the part after `.next/server`
133
135
  const cacheFile = path_1.default.join(cacheDir, file.slice(pageRoot.length + 1));
134
136
  if ((0, fs_1.existsSync)(cacheFile)) {
135
137
  return statsOrig(cacheFile, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.28.3",
3
+ "version": "4.28.4-appdir.0",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -13,6 +13,12 @@ export interface FetchEventResult {
13
13
  waitUntil: Promise<any>
14
14
  }
15
15
 
16
+ export interface I18NConfig {
17
+ defaultLocale: string
18
+ localeDetection?: false
19
+ locales: string[]
20
+ }
21
+
16
22
  export interface RequestData {
17
23
  geo?: {
18
24
  city?: string
@@ -26,7 +32,7 @@ export interface RequestData {
26
32
  method: string
27
33
  nextConfig?: {
28
34
  basePath?: string
29
- i18n?: Record<string, unknown>
35
+ i18n?: I18NConfig | null
30
36
  trailingSlash?: boolean
31
37
  }
32
38
  page?: {
@@ -50,10 +56,10 @@ declare global {
50
56
  globalThis.NFRequestContextMap ||= new Map()
51
57
 
52
58
  const handler = async (req: Request, context: Context) => {
53
- if (Deno.env.get('NETLIFY_DEV')) {
54
- // Don't run in dev
55
- return
56
- }
59
+ // if (Deno.env.get('NETLIFY_DEV')) {
60
+ // // Don't run in dev
61
+ // return
62
+ // }
57
63
 
58
64
  const url = new URL(req.url)
59
65