【概括】本文升级整体按照官网文档指引进行,在迁移 pages Router 前先看了官网的实操视频。
【注意】文章内对 .babel.ts、next.config.js 进行了多次更改,最终配置可见 报错3: Server Error ReferenceError: React is not defined
一、升级 Next.js 版本
npm install next@latest react@latest react-dom@latest
升级后报错:
报错1:Build Error :You may need an appropriate loader to handle this file type, currently no loaders areconfigured to process this file
file type, currently no loaders areconfigured to process this file.
Failed to compile ●Next.js (14.2.3)
/components/GlobalLoading/index.module.less Module parse failed:
Unexpected token(1:0)You may need an appropriate loader to handle this
file type, currently no loaders areconfigured to process this file.
See https://webpack.js.org/concepts#loaders.container f
pointer-events: none; transition-property: opacity;
原因:next 内部支持 css 而不支持 less,我的项目在 next v12.1.6 时使用 next-plugin-antd-less 来支持 less,但根据官方文档来看,它只实现了对 next v11、v12 的支持,不支持 v14,因此我们选择安装其他包来支持。
解决方法:
npm install less less-loader next-with-less
配置:
// next.config.js
const withLess = require("next-with-less");
module.exports = withLess({
lessLoaderOptions: {
/* 里面的配置根据自己的来,下面是我之前next-plugin-antd-less里的配置 */
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#4169E1',
'@primary-deep': '#3656F8',
'@background-color': '#F4F4F4',
},
additionalData: `@import "${__dirname}/src/styles/variables.less";`,
},
});
参考文章:
如何在 next.js 13.4.13 中使用 less?
Next.js 和 Antd 以及 CSS Less 设置
next-with-less
Next13支持less&自定义less-module
报错2:Build Error:Syntax error: Selector “:global .ant-menu-sub” is not pure (pure selectors must contain at least one local class or id)
原因:scss 不期望在模块样式文件中使用全局选择器,我们最好使用类选择器,然后在类选择器内使用其他选择器。
解决方法:不要在 .module.scss 的第一层选择器使用 :global 或 标签选择器;如果需要全局拦截,考虑添加一个全局 css 文件(global.css)然后在 layout.tsx 里引入
// .module.scss,下面的写法是可以的
.root {
a {
color: red;
}
}
参考文档:Selector “:global .class” is not pure
报错3:Build Error:Global CSS cannot be imported from within node_modules.
解决办法:我参考了很多文档,但还是没明白根本原因。我这里觉得是 less 文件的问题,因此修改了 webpack 配置,修改后在我本地可以正常运行。(后面觉得 next 对 less 的支持很不友好,于是放弃了 less,采用了 scss, 因此下面这段 webpack 配置最终也被替换)
// next.config.js
webpack(config, { isServer }) {
config.module.rules.push({
test: /\.less$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
},
},
},
],
});
return config;
},
// .babelrc.js
module.exports = {
presets: ['next/babel'],
// 下面这行注释了,我这次更新就不用 antd 了,所以直接注释,升级完会把和 antd 相关的都删了
// plugins: [['import', { libraryName: 'antd', style: true }]],
};
二、升级新功能
升级 Image
npx @next/codemod@latest next-image-to-legacy-image .
升级 Link
npx @next/codemod@latest new-link .
升级 Script
我没用到 Script ,升级可看官网
三、迁移 pages 到 app
报错1:TypeError: Property left of AssignmentExpression expected node to be of a type [“LVal”] but instead got “BooleanLiteral”
解决方法:参考下面文章,评论回复 .babelrc.js 文件在不必要时可删除,因此我删除后可以正常运行了。但在我继续升级中,.babelrc.js 还是增加了如下配置:
module.exports = {
"presets": [
'next/babel',
],
"plugins": []
}
参考文章:Upgrading nextjs from 12 to 14 and receiving Boolean Literal type error on node_modules/process/browser.js when trying to access env variables
报错2:Support for the experimental syntax ‘jsx’ isn’t currently enabled
解决方法: 安装两个包并配置如下内容
npm install --save-dev @babel/preset-env @babel/preset-react
// .babelrc.js
module.exports = {
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
参考文章:Support for the experimental syntax ‘jsx’ isn’t currently enabled
报错3: Server Error ReferenceError: React is not defined
总结:
把 less 换成 scss 了
// next.config.js
const path = require('path')
const SassLoader = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
prependData: `
$primary-color: #4169E1;
$primary-deep: #3656F8;
$background-color: #F4F4F4;
`,
},
}
// babelrc.ts
module.exports = {
"presets": [
'next/babel',
],
}
报错4: app-index.js:33 Warning: A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.
解决方法:当前文件顶部添加 'use client'
报错5: Server Error Error: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function
解决方法:当前文件顶部添加 'use client'
报错6: Server Error Error: Super expression must either be null or a function
解决方法:当前文件顶部添加 'use client'
App Router SEO 处理
在未升级前,我通过 url.pathname 为不同页面设置不同的 title、 content:
<Head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
</Head>
但在升级之后,这些内容由 layout.tsx 的 generateMetadata 暴露出去,在 server component 中我无法直接获取 url 的 pathname
解决方法:通过中间件的形式注入 pathname,保证在 server component 里也可以获取到页面路径。采用设置 x-url 而不是直接读取请求头的 referer ,是因为直接输入 url 访问时 headers 没有 referer。
// middleware.ts
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-url', request.url);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set('x-url', request.url)
return response
}
// layout.tsx
export async function generateMetadata(): Promise<Metadata | null> {
const url = new URL(headers().get("x-url")!);
let seoData = null
try {
seoData = seo(url.pathname)
} catch (error) {
}
return seoData
}
middleware.ts 配置参考:
官方文档
github: How to access pathname in generateMetadata?