NextJS FAQ
caution
- react router 会导致页面整个刷新 #49479
- NextJS Server
- 尽量避免太重的 Server
- 导致前端开发慢
- 依赖可能出现各种问题
 
- Server 环境复杂 - middleware、server action、edge
- 使用可能有各种注意事项
- Server 会初始化多次
 
- Server 会导致环境依赖
- 复用度降低
 
- 如果不需要 服务端渲染/SSR/SEO 尽量避免过多 Server 代码
 
- 尽量避免太重的 Server
环境变量
- dotenv 会自动 reload
- .env 加载顺序
- .env.$(NODE_ENV).local- development production test
- 不该加到 git 参考
 
- .env.local
- 不会打包到 standalone
- test 不会加载
 
- .env.$(NODE_ENV)
- .env
- 会被打包到 standalone
 
 
// 测试加载
import { loadEnvConfig } from '@next/env';
export default async () => {
  const projectDir = process.cwd();
  loadEnvConfig(projectDir);
};
- 默认暴露到前端 NEXT_PUBLIC_
- NEXTAUTH_URL
- NEXT_PUBLIC_VERCEL_URL
- 参考
| env | demo | production | 
|---|---|---|
| BASE_PATH | /auth | |
| NEXT_PUBLIC_URL | http://127.0.0.1:$PORT | https://wener.me | 
| NEXT_PUBLIC_BASE_PATH | $BASE_PATH  | |
| NEXT_PUBLIC_BASE_URL | $NEXT_PUBLIC_URL$BASE_PATH | |
| NEXTAUTH_URL | ||
| PORT | 3000 | |
| NEXTAUTH_URL | http://127.0.0.1:$PORT/auth/api/auth | https://wener.me/auth/api/auth | 
| NEXTAUTH_URL_INTERNAL | http://127.0.0.1:$PORT/auth/api/auth | |
| NEXTAUTH_SECRET | 生产环境必须 | |
| NEXT_PUBLIC_VERCEL_URL | 
PORT=3000
BASE_PATH=/auth
NEXTAUTH_URL=http://127.0.0.1:$PORT/auth/api/auth
NEXTAUTH_URL_INTERNAL=http://127.0.0.1:$PORT/auth/api/auth
NEXTAUTH_SECRET=
避免 Build 预渲染
Build 预渲染 需要构建环境有完备的 ENV,比如数据库连接、接口请求都要能正常返回。
next build --experimental-build-mode compile
# next experimental-compile
Render Context
- 静态渲染 - 预渲染阶段
- 根据情况有时候可能需要,有时候可能不希望需要
- 预渲染后的内容是纯静态的
- 调用 cookies, headers, fetch 会 opt 为 服务端渲染
- 纯静态内容相当于直接生成了文件
 
- 服务端渲染
- 能 async
- 能很容易获取到数据
- 能获取到的上下文信息较少
- 不能包含任何 state、Context 的代码
- 只能使用部分 React 功能
 
- 客户端渲染
- 能使用所有 React 功能
- 根据构建的站点情况,可能需要对数据获取做特殊处理
- 例如:使用 action 来避免请求到真实的服务器,避免暴露真实服务器信息
 
- 通常不要求 静态渲染/预渲染,除非是非常大流量的站点,不期望任何额外的请求到服务端后端。
- 因此大多数时候需要区分 服务端渲染/SSR 还是 客户端渲染/CSR
- 营销、推广、官网 站点 尽可能的让代码能 SSR
- 让代码能 SSR 的一些方式
- 尽量使用 CSS 做一些效果
- 例如 tab 切换、按钮效果
 
- 使用统一的一个 CSR 组件维护全局状态
- 例如 弹窗、按钮 action 处理
 
- 尽量隔离 CSR 组件
- 可能一个 CSR 组件只做一个事情
- 例如 ShowContactButton 只是在 Button 的基础上增加了 onClick 操作
 
 
- 尽量使用 CSS 做一些效果
socket hang up - 30s timeout
- rewrite 时出现
- 本地测试可以 Disabling HTTP Keep-Alive
- 线上可以配置 experimental.proxyTimeout
module.exports = {
  httpAgentOptions: {
    keepAlive: false,
  },
  experimental: {
    proxyTimeout: 30_000, // 默认 30s
  },
};
pathname vs asPath
| path | pathname | asPath | 
|---|---|---|
| / | / | / | 
| /user/123 | /user/[id] | /user/123 | 
| /user/123#top | /user/[id] | /user/123#top | 
- asPath 真实路径
- pathname NextJS 的文件路径
server vs serverless
- server
- 打包为整个应用
- 支持自定义 server
- 支持长链接 - websocket
- 建议使用
 
- serverless
- 不会构建一体化应用
- 页面独立 - 服务与页面不耦合
- 页面与服务分离后更容易部署
- 但依然是需要 next 来运行 - #4496
 
- vercel 默认模式
 
- 参考
next.config.ts
next.config.js
require('ts-node').register(require('./tsconfig.json'));
module.exports = require('./next.config.ts');
next.config.js 类型提示
/** @type {import('next').NextConfig} */
module.exports = {};
Prop className did not match
nextjs 12 后需要配置 compiler
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    // 添加
    styledComponents: true,
  },
};
module.exports = nextConfig;
使用单一的 API 来处理所有接口
yarn add polka cors body-parser
import polka from 'polka';
import cors from 'cors';
import { json, text, urlencoded } from 'body-parser';
let _router;
export function getRouter() {
  return _router || (_router = routes(setup(polka())));
}
function setup(route) {
  // treat path params as query - same as how next api handle this
  route.use((req, res, next) => {
    if (req.params) {
      const q = req.query;
      for (const [k, v] of Object.entries(req.params)) {
        if (!q[k]) {
          q[k] = v;
        }
      }
    }
    return next();
  });
  return route;
}
export function routes(r: any) {
  const route = r as Router<NextApiRequest, NextApiResponse>;
  // handle error
  route.use(async (req, res, next) => {
    try {
      return await next();
    } catch (e) {
      const detail = normalizeError(e);
      res.status(detail.status).json(detail);
      console.error(`ERROR Handle ${req.url}`, e);
    }
  });
  route.use(json());
  route.use(urlencoded({ extended: true }));
  route.use(text());
  const corsOrigin = cors({ origin: true });
  // cors
  route.get('/api/cors', corsOrigin as any, (req, res) => res.json({}));
  // path params
  route.get('/api/user/:id', corsOrigin as any, (req, res) => res.json({ id: req.query.id }));
  return route;
}
export function handleRequest(req, res) {
  getRouter().handler(req, res);
}
export default handleRequest;
export const config = {
  api: {
    bodyParser: false,
  },
};
Critical dependency: the request of a dependency is an expression
构建为 serverless 时可能出现
禁用 minification
export default {
  productionBrowserSourceMaps: true,
  webpack(config) {
    config.optimization.minimize = false;
    config.optimization.minimizer = [];
    return config;
  },
};
css 顺序不一致
将 app 导入的 css 抽取放到一个 css 文件进行导入
@import '~normalize.css/normalize.css';
@import '~@blueprintjs/icons/lib/css/blueprint-icons.css';
@import '~@blueprintjs/core/lib/css/blueprint.css';
@import '~@blueprintjs/select/lib/css/blueprint-select.css';
@import '~@blueprintjs/datetime/lib/css/blueprint-datetime.css';
@import '~@blueprintjs/popover2/lib/css/blueprint-popover2.css';
@import '~@blueprintjs/table/lib/css/table.css';
@import '~tailwindcss/tailwind.css';
@import '~nprogress/nprogress.css';
确保 tailwind 覆盖 blueprintjs 样式
- 开启 webpack5 该方式无效
- #16630
访问 public 里的 index.html
module.export = {
  async rewrites() {
    return {
      fallback: [
        {
          source: '/:path*',
          destination: '/:path*/index.html',
        },
      ],
    };
  },
};
dynamic rewrites
- 不支持动态,构建时生成的 manifest
- https://github.com/vercel/next.js/discussions/33932
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    // 会在构建时静态生成,不支持动态
    const { SERVER_URL } = process.env;
    let redir = [
      {
        source: '/api/:path*',
        destination: `${SERVER_URL}/api/:path*`,
      },
    ];
    return redir;
  },
};
Lockfile was successfully patched, please run "npm install" to ensure @next/swc dependencies are downloaded
standalone
- 非常适用于 docker 环境
- 只需要 .next/standalone 不需要 node_modules
- 使用 @vercel/nft 分析 import
module.exports = {
  output: 'standalone',
  experimental: {
    // monorepo 需要调整 root
    outputFileTracingRoot: path.join(__dirname, '../../'),
  },
};
FROM node:16-alpine
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# monorepo need prefix path
COPY next.config.js ./apps/web/
COPY public ./apps/web/public
COPY package.json ./apps/web/package.json
COPY --chown=nextjs:nodejs .next/standalone ./
COPY --chown=nextjs:nodejs .next/static ./apps/web/.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# monorepo
CMD ["node", "apps/web/server.js"]
- 由于 hosting 的原因,可能出现模块无法找到的问题
- pnpm 重复 build 会有问题
- 基于 @vercel/nft 跟踪依赖
- ntf -> Node File Trace
 
- https://nextjs.org/docs/advanced-features/output-file-tracing#automatically-copying-traced-files-experimental
- https://github.com/vercel/next.js/discussions/16995
dynamic preload
- dyanmic 不会预加载 - prefetch
- 使用 @loadable/component
safari tel
<meta name="format-detection" content="telephone=no" />
检测在浏览器
// Webpack 定义 - 会根据依赖来移除代码
// https://github.com/zeit/next.js/pull/5159
process.browser;
// 通用逻辑
typeof window === 'undefined';
如何在 getInitialProps 引入服务端模块
- 如果直接 require 是会导致被打包到客户端代码的
const faker = eval("require('faker')");
这个方式是最简单的,其他方式参考 SSR and Server Only Modules
Invalid left-hand side in assignment "MyModule*" = namespaces;
- using npm debug module breaks build (overriding any property on process.env)
- debug 模块的问题 - 注意引入位置 - 如果通过 transpile 可能会有问题
分析服务端代码和 SSR 代码
安装依赖
npm install --save-dev webpack-bundle-analyzer @zeit/next-bundle-analyzer
next.config.js
const withBundleAnalyzer = require('@zeit/next-bundle-analyzer');
const nextConfig = {
  analyzeServer: ['server', 'both'].includes(process.env.BUNDLE_ANALYZE),
  analyzeBrowser: ['browser', 'both'].includes(process.env.BUNDLE_ANALYZE),
  bundleAnalyzerConfig: {
    server: {
      analyzerMode: 'static',
      reportFilename: '../bundles/server.html',
    },
    browser: {
      analyzerMode: 'static',
      reportFilename: '../bundles/client.html',
    },
  },
  webpack(config) {
    return config;
  },
};
module.exports = withBundleAnalyzer(nextConfig);
添加脚本
添加到 package.json
"analyze": "BUNDLE_ANALYZE=both next build",
"analyze:server": "BUNDLE_ANALYZE=server next build",
"analyze:browser": "BUNDLE_ANALYZE=browser next build"
执行分析
npm run analyze
You're using a Node.js module (buffer) which is not supported in the Edge Runtime
- Buffer.from -> atob/btoa
- https://nextjs.org/docs/api-reference/edge-runtime
Expected server HTML to contain a matching div
function App({ Component, pageProps }: AppProps) {
  return (
    <div suppressHydrationWarning> // <- ADD THIS
      {typeof window === 'undefined' ? null : <Component {...pageProps} />}
    </div>
  );
}
SPA rewrites
next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/:any*',
        destination: '/',
      },
    ];
  },
};
Date cannot be serialized as JSON
superjson
Module build failed: UnhandledSchemeError: Reading from "node:assert" is not handled by plugins (Unhandled scheme).
next.config.js
const webpack = require('webpack');
module.exports = {
  webpack: (config, options) => {
    config.plugins.push(
      new webpack.NormalModuleReplacementPlugin(/^node:/, (resource) => {
        resource.request = resource.request.replace(/^node:/, '');
      }),
    );
    return config;
  },
};
- middleware 运行环境限制很多
- webpack 不支持 node 协议
- https://github.com/vercel/next.js/issues/28774
Can't resolve 'assert'
npm add assert
- node:assert -> assert
Creating an optimized production build
DYNAMIC_SERVER_USAGE
dynamic ssr:false 时出现
Warning: lazy: Expected the result of a dynamic import() call.
NextJS ssr 不支持 React.lazy
Cannot read properties of null (reading 'useRef')
- SSR, Hook
- appDir: false 还是出现
- "use client"
- zustand#1395
- https://github.com/vercel/next.js/issues/42263
库 import 必须要有 .js 后缀
- 因为 { "type": "module" },{ "resolution": "NodeNext" }定义如此
- 但是 swc compile 出来没有
- https://github.com/vercel/next.js/discussions/32237
- --experimental-specifier-resolution=node- 允许没有 .js 后缀
 
- https://stackoverflow.com/questions/62619058
NODE_OPTIONS=--experimental-specifier-resolution=node
process.env.NEXT_PHASE
export const PHASE_EXPORT = 'phase-export'
export const PHASE_PRODUCTION_BUILD = 'phase-production-build'
export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
export const PHASE_TEST = 'phase-test'
export const PHASE_INFO = 'phase-info'
Failed to find Server Action. This request might be from an older or newer deployment.
await isn't allowed in non-async function
- NextJS 14, Server Action
await __webpack_async_dependencies__
const config = {
  experimental: {
    // https://github.com/vercel/next.js/discussions/57535
    esmExternals: false,
  },
};