如何从 Create React App 迁移到 Next.js
本指南将帮助您将现有的 Create React App (CRA) 站点迁移到 Next.js。
为什么要切换?
您可能希望从 Create React App 切换到 Next.js 的原因有以下几点:
初始页面加载时间慢
Create React App 纯粹使用客户端渲染。仅客户端应用程序,也称为单页应用程序 (SPA),通常会遇到初始页面加载时间慢的问题。这通常是由于以下几个原因造成的:
- 浏览器需要等待 React 代码和整个应用程序包下载并运行,然后您的代码才能发送请求以加载数据。
- 您的应用程序代码随着您添加的每个新功能和依赖项而增长。
无自动代码分割
通过代码分割可以在一定程度上缓解加载时间慢的问题。但是,如果您尝试手动进行代码分割,可能会无意中引入网络瀑布。Next.js 的路由器和构建管道中内置了自动代码分割和摇树优化。
网络瀑布
性能不佳的一个常见原因是应用程序进行连续的客户端-服务器请求来获取数据。在SPA中获取数据的一种模式是渲染一个占位符,然后在组件挂载后获取数据。不幸的是,子组件只有在父组件完成加载其自身数据后才能开始获取数据,从而导致请求的“瀑布”。
虽然 Next.js 支持客户端数据获取,但 Next.js 也允许您将数据获取转移到服务器。这通常可以完全消除客户端-服务器瀑布。
快速且有意的加载状态
通过内置的通过 React Suspense 进行流式传输支持,您可以定义 UI 的哪些部分先加载以及加载顺序,而不会创建网络瀑布。
这使您能够构建加载速度更快的页面并消除布局偏移。
选择数据获取策略
根据您的需求,Next.js 允许您在页面或组件级别选择数据获取策略。例如,您可以从 CMS 获取数据并在构建时(SSG)渲染博客文章以实现快速加载,或者在必要时在请求时(SSR)获取数据。
代理
Next.js 代理允许您在请求完成之前在服务器上运行代码。例如,您可以通过在代理中将用户重定向到仅限已验证页面的登录页面来避免未经身份验证内容的闪烁。您还可以将其用于 A/B 测试、实验和国际化等功能。
内置优化
图片、字体和第三方脚本通常对应用程序的性能影响很大。Next.js 包含专门的组件和 API,可以自动为您优化它们。
迁移步骤
我们的目标是尽快获得一个可工作的 Next.js 应用程序,以便您可以逐步采用 Next.js 功能。首先,我们将您的应用程序视为纯客户端应用程序(SPA),而不立即替换您现有的路由器。这减少了复杂性和合并冲突。
注意:如果您正在使用高级 CRA 配置,例如
package.json
中的自定义homepage
字段、自定义 Service Worker 或特定的 Babel/webpack 调整,请参阅本指南末尾的“其他注意事项”部分,了解如何在 Next.js 中复制或调整这些功能的提示。
步骤 1:安装 Next.js 依赖
在现有项目中安装 Next.js
npm install next@latest
步骤 2:创建 Next.js 配置文件
在项目根目录(与 package.json
同级)创建 next.config.ts
文件。此文件包含您的Next.js 配置选项。
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA)
distDir: 'build', // Changes the build output directory to `build`
}
export default nextConfig
注意:使用
output: 'export'
意味着您正在进行静态导出。您将无法访问 SSR 或 API 等服务器端功能。您可以删除此行以利用 Next.js 服务器功能。
步骤 3:创建根布局
Next.js App Router 应用程序必须包含一个根布局文件,它是一个将包装所有页面的React 服务器组件。
CRA 应用程序中最接近根布局文件的等效项是 public/index.html
,它包含您的 <html>
、<head>
和 <body>
标签。
- 在您的
src
文件夹内(如果您更喜欢将app
放在根目录,则在项目根目录)创建一个新的app
目录。 - 在
app
目录中,创建一个layout.tsx
(或layout.js
)文件。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
现在将旧 index.html
的内容复制到此 <RootLayout>
组件中。将 body div#root
(和 body noscript
)替换为 <div id="root">{children}</div>
。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
须知:Next.js 默认会忽略 CRA 的
public/manifest.json
、额外图标和测试配置。如果您需要这些,Next.js 通过其元数据 API 和测试设置提供支持。
步骤 4:元数据
Next.js 自动包含 <meta charset="UTF-8" />
和 <meta name="viewport" content="width=device-width, initial-scale=1" />
标签,因此您可以将它们从 <head>
中移除。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
任何元数据文件,例如 `favicon.ico`、`icon.png`、`robots.txt`,只要您将它们放置在 `app` 目录的顶层,就会自动添加到应用程序的 `
` 标签中。将所有支持的文件移动到 `app` 目录后,您可以安全地删除它们的 `` 标签。export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
最后,Next.js 可以使用 Metadata API 管理您最终的 `
` 标签。将您最终的元数据信息移动到一个导出的 `metadata` 对象中。import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
通过上述更改,您已从在 `index.html` 中声明所有内容转向使用 Next.js 基于约定的方法(Metadata API)。这种方法使您能够更轻松地改善页面的 SEO 和 Web 可共享性。
步骤 5:样式
与 CRA 类似,Next.js 开箱即支持CSS Modules。它还支持全局 CSS 导入。
如果您有一个全局 CSS 文件,请将其导入到您的 app/layout.tsx
中。
import '../index.css'
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
如果您正在使用 Tailwind CSS,请参阅我们的安装文档。
步骤 6:创建入口页面
Create React App 使用 src/index.tsx
(或 index.js
)作为入口点。在 Next.js (App Router) 中,app
目录中的每个文件夹都对应一个路由,并且每个文件夹都应该有一个 page.tsx
。
由于我们暂时希望将应用程序保留为 SPA 并拦截所有路由,因此我们将使用可选的捕获所有路由。
- 在
app
中创建一个[[...slug]]
目录。
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
- 将以下内容添加到
page.tsx
中::
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
这告诉 Next.js 为空 slug (/
) 生成一个单一路由,有效地将所有路由映射到同一个页面。此页面是一个服务器组件,预渲染为静态 HTML。
步骤 7:添加仅客户端入口点
接下来,我们将您的 CRA 的根 App 组件嵌入到一个客户端组件中,以便所有逻辑都保留在客户端。如果这是您第一次使用 Next.js,值得了解的是,客户端组件(默认情况下)仍然在服务器上进行预渲染。您可以将它们视为具有运行客户端 JavaScript 的附加功能。
在 app/[[...slug]]/
中创建 client.tsx
(或 client.js
)。
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
指令使此文件成为一个客户端组件。- 带有
ssr: false
的dynamic
导入会禁用<App />
组件的服务器端渲染,使其成为真正的纯客户端 (SPA) 组件。
现在更新您的 page.tsx
(或 page.js
)以使用您的新组件。
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
步骤 8:更新静态图片导入
在 CRA 中,导入图片文件会将其公共 URL 作为字符串返回。
import image from './img.png'
export default function App() {
return <img src={image} />
}
在 Next.js 中,静态图像导入返回一个对象。该对象可以直接与 Next.js `<Image>` 组件一起使用,或者您可以使用对象的 `src` 属性与您现有的 `<img>` 标签一起使用。
`<Image>` 组件具有自动图片优化的额外优势。`<Image>` 组件根据图片的尺寸自动设置生成的 `<img>` 的 `width` 和 `height` 属性。这可以防止图片加载时出现布局偏移。但是,如果您的应用程序中包含只有其一个尺寸被样式化而另一个没有样式化为 `auto` 的图片,这可能会导致问题。当没有样式化为 `auto` 时,该尺寸将默认为 `<img>` 尺寸属性的值,这可能会导致图片失真。
保留 `<img>` 标签将减少应用程序中的更改量并防止上述问题。然后,您可以选择稍后迁移到 `<Image>` 组件,以利用通过配置加载器优化图片,或迁移到具有自动图片优化的默认 Next.js 服务器。
将从 `public` 导入的图片的绝对导入路径转换为相对导入。
// Before
import logo from '/logo.png'
// After
import logo from '../public/logo.png'
将图片 `src` 属性(而不是整个图片对象)传递给您的 `<img>` 标签。
// Before
<img src={logo} />
// After
<img src={logo.src} />
或者,您可以根据文件名引用图像资源的公共 URL。例如,`public/logo.png` 将在您的应用程序中以 `/logo.png` 提供图像,这将是 `src` 值。
警告:如果您正在使用 TypeScript,访问
src
属性时可能会遇到类型错误。要解决这些问题,您需要将next-env.d.ts
添加到您的tsconfig.json
文件的include
数组中。当您在步骤 9 运行应用程序时,Next.js 将自动生成此文件。
步骤 9:迁移环境变量
Next.js 支持环境变量与 CRA 类似,但要求任何您希望在浏览器中公开的变量都带有 NEXT_PUBLIC_
前缀。
主要区别在于用于在客户端公开环境变量的前缀。将所有带有 REACT_APP_
前缀的环境变量更改为 NEXT_PUBLIC_
。
步骤 10:更新 package.json
中的脚本
更新您的 package.json
脚本以使用 Next.js 命令。另外,将 .next
和 next-env.d.ts
添加到您的 .gitignore
中。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ...
.next
next-env.d.ts
现在你可以运行
npm run dev
打开 https://:3000。您应该会看到您的应用程序现在正在 Next.js 上运行(以 SPA 模式)。
步骤 11:清理
您现在可以删除特定于 Create React App 的工件。
public/index.html
src/index.tsx
src/react-app-env.d.ts
reportWebVitals
设置react-scripts
依赖项(从package.json
中卸载它)
其他注意事项
在 CRA 中使用自定义 homepage
如果您在 CRA 的 package.json
中使用 homepage
字段在特定子路径下提供应用程序,您可以使用 next.config.ts
中的basePath
配置在 Next.js 中复制此功能。
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfig
处理自定义 Service Worker
如果您使用了 CRA 的 service worker(例如 create-react-app
中的 serviceWorker.js
),您可以了解如何使用 Next.js 创建渐进式 Web 应用程序 (PWA)。
代理 API 请求
如果您的 CRA 应用程序使用 package.json
中的 proxy
字段将请求转发到后端服务器,您可以使用 next.config.ts
中的Next.js 重写来复制此功能。
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://your-backend.com/:path*',
},
]
},
}
自定义 Webpack
如果您在 CRA 中有自定义的 webpack 或 Babel 配置,您可以在 next.config.ts
中扩展 Next.js 的配置。
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config, { isServer }) => {
// Modify the webpack config here
return config
},
}
export default nextConfig
注意:这将需要通过在您的
dev
脚本中添加--webpack
来使用 Webpack。
TypeScript 设置
如果您有 tsconfig.json
,Next.js 会自动设置 TypeScript。确保 next-env.d.ts
列在您的 tsconfig.json
的 include
数组中。
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}
打包器兼容性
Create React App 使用 webpack 进行打包。Next.js 现在默认使用Turbopack以实现更快的本地开发。
next dev # Uses Turbopack by default
改为使用 Webpack(类似于 CRA)
next dev --webpack
如果您需要从 CRA 迁移高级 webpack 设置,仍然可以提供自定义 webpack 配置。
后续步骤
如果一切正常,您现在就拥有了一个功能正常的 Next.js 应用程序,它以单页应用程序的形式运行。您尚未利用 Next.js 的服务器端渲染或基于文件的路由等功能,但您现在可以逐步实现这些功能。
- 将 React Router 迁移到Next.js App Router,以实现:
- 自动代码分割
- 流式服务器渲染
- React 服务器组件
- 使用
<Image>
组件优化图片 - 使用
next/font
优化字体 - 使用
<Script>
组件优化第三方脚本 - 使用 Next.js 推荐规则启用 ESLint
注意:目前,使用静态导出(
output: 'export'
)不支持useParams
钩子或其他服务器功能。要使用所有 Next.js 功能,请从next.config.ts
中删除output: 'export'
。
这有帮助吗?