从 Create React App 迁移
本指南将帮助你将现有的 Create React App 站点迁移到 Next.js。
¥This guide will help you migrate an existing Create React App site to Next.js.
为什么要切换?
¥Why Switch?
你可能想要从 Create React App 切换到 Next.js 有几个原因:
¥There are several reasons why you might want to switch from Create React App to Next.js:
初始页面加载时间慢
¥Slow initial page loading time
Create React App 使用纯粹的客户端 React。仅客户端应用,也称为单页应用 (SPA),通常会遇到初始页面加载时间缓慢的情况。发生这种情况有以下几个原因:
¥Create React App uses purely client-side React. Client-side only applications, also known as single-page applications (SPAs), often experience slow initial page loading time. This happens due to a couple of reasons:
-
浏览器需要等待 React 代码和整个应用包下载并运行,然后代码才能发送加载数据的请求。
¥The browser needs to wait for the React code and your entire application bundle to download and run before your code is able to send requests to load data.
-
你的应用代码随着你添加的每个新功能和依赖而增长。
¥Your application code grows with every new feature and dependency you add.
没有自动代码分割
¥No automatic code splitting
上一个加载时间慢的问题可以通过代码分割来解决。但是,如果你尝试手动进行代码分割,通常会导致性能变差。手动代码分割时很容易无意中引入网络瀑布。Next.js 提供内置于其路由中的自动代码分割。
¥The previous issue of slow loading times can be somewhat managed with code splitting. However, if you try to do code splitting manually, you'll often make performance worse. It's easy to inadvertently introduce network waterfalls when code-splitting manually. Next.js provides automatic code splitting built into its router.
网络瀑布
¥Network waterfalls
当应用发出连续的客户端-服务器请求来获取数据时,会出现性能不佳的常见原因。SPA 中获取数据的一种常见模式是首先渲染占位符,然后在安装组件后获取数据。不幸的是,这意味着获取数据的子组件在父组件完成加载自己的数据之前无法开始获取。
¥A common cause of poor performance occurs when applications make sequential client-server requests to fetch data. One common pattern for data fetching in an SPA is to initially render a placeholder, and then fetch data after the component has mounted. Unfortunately, this means that a child component that fetches data can't start fetching until the parent component has finished loading its own data.
虽然 Next.js 支持在客户端获取数据,但它还为你提供了将数据获取转移到服务器的选项,这可以消除客户端-服务器瀑布。
¥While fetching data on the client is supported with Next.js, it also gives you the option to shift data fetching to the server, which can eliminate client-server waterfalls.
快速且有意的加载状态
¥Fast and intentional loading states
借助对 通过 React Suspense 进行流式传输 的内置支持,你可以更加有意识地了解要首先加载 UI 的哪些部分以及按什么顺序加载,而无需引入网络瀑布。
¥With built-in support for streaming through React Suspense, you can be more intentional about which parts of your UI you want to load first and in what order without introducing network waterfalls.
这使你能够构建加载速度更快的页面并消除 布局变化。
¥This enables you to build pages that are faster to load and eliminate layout shifts.
选择数据获取策略
¥Choose the data fetching strategy
根据你的需求,Next.js 允许你在页面和组件的基础上选择数据获取策略。你可以决定在构建时、在服务器上请求时或在客户端上获取。例如,你可以从 CMS 获取数据并在构建时渲染你的博客文章,然后可以将其有效地缓存在 CDN 上。
¥Depending on your needs, Next.js allows you to choose your data fetching strategy on a page and component basis. You can decide to fetch at build time, at request time on the server, or on the client. For example, you can fetch data from your CMS and render your blog posts at build time, which can then be efficiently cached on a CDN.
中间件
¥Middleware
Next.js 中间件 允许你在请求完成之前在服务器上运行代码。当用户通过将用户重定向到登录页面来访问仅经过身份验证的页面时,这对于避免出现未经身份验证的内容特别有用。该中间件对于实验和 internationalization 也很有用。
¥Next.js Middleware allows you to run code on the server before a request is completed. This is especially useful to avoid having a flash of unauthenticated content when the user visits an authenticated-only page by redirecting the user to a login page. The middleware is also useful for experimentation and internationalization.
内置优化
¥Built-in Optimizations
图片、fonts 和 第三方脚本 通常对应用的性能有重大影响。Next.js 附带内置组件,可以自动为你优化这些组件。
¥Images, fonts, and third-party scripts often have significant impact on an application's performance. Next.js comes with built-in components that automatically optimize those for you.
迁移步骤
¥Migration Steps
我们此次迁移的目标是尽快获得可用的 Next.js 应用,以便你可以逐步采用 Next.js 功能。首先,我们将其保留为纯粹的客户端应用 (SPA),而无需迁移现有路由。这有助于最大限度地减少迁移过程中遇到问题的机会并减少合并冲突。
¥Our goal with this migration is to get a working Next.js application as quickly as possible, so that you can then adopt Next.js features incrementally. To begin with, we'll keep it as a purely client-side application (SPA) without migrating your existing router. This helps minimize the chances of encountering issues during the migration process and reduces merge conflicts.
步骤 1:安装 Next.js 依赖
¥Step 1: Install the Next.js Dependency
你需要做的第一件事是将 next
安装为依赖:
¥The first thing you need to do is to install next
as a dependency:
npm install next@latest
步骤 2:创建 Next.js 配置文件
¥Step 2: Create the Next.js Configuration File
在项目的根目录下创建一个 next.config.mjs
。该文件将保存你的 Next.js 配置选项。
¥Create a next.config.mjs
at the root of your project. This file will hold your Next.js configuration options.
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './build', // Changes the build output directory to `./dist`.
}
export default nextConfig
步骤 3:创建根布局
¥Step 3: Create the Root Layout
Next.js 应用路由 应用必须包含 根布局 文件,该文件是一个 React 服务器组件 文件,它将封装应用中的所有页面。该文件定义在 app
目录的顶层。
¥A Next.js App Router application must include a root layout file, which is a React Server Component that will wrap all pages in your application. This file is defined at the top level of the app
directory.
与 CRA 应用中的根布局文件最接近的等效项是 index.html
文件,其中包含 <html>
、<head>
和 <body>
标签。
¥The closest equivalent to the root layout file in a CRA application is the index.html
file, which contains your <html>
, <head>
, and <body>
tags.
在此步骤中,你将把 index.html
文件转换为根布局文件:
¥In this step, you'll convert your index.html
file into a root layout file:
-
在
src
目录中创建一个新的app
目录。¥Create a new
app
directory in yoursrc
directory. -
在
app
目录中创建一个新的layout.tsx
文件:¥Create a new
layout.tsx
file inside thatapp
directory:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
export default function RootLayout({ children }) {
return '...'
}
很高兴知道:
.js
、.jsx
或.tsx
扩展名可用于布局文件。¥Good to know:
.js
,.jsx
, or.tsx
extensions can be used for Layout files.
将 index.html
文件的内容复制到之前创建的 <RootLayout>
组件中,同时用 <div id="root">{children}</div>
替换 body.div#root
和 body.noscript
标签:
¥Copy the content of your index.html
file into the previously created <RootLayout>
component while replacing the body.div#root
and body.noscript
tags with <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>
)
}
export default function RootLayout({ children }) {
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 也支持这些选项。
¥Good to know: We'll ignore the manifest file, additional iconography other than the favicon, and testing configuration, but if these are requirements, Next.js also supports these options.
步骤 4:元数据
¥Step 4: Metadata
Next.js 默认情况下已包含 元字符集 和 元视口 标签,因此你可以安全地从 <head>
中删除这些标签:
¥Next.js already includes by default the meta charset and meta viewport tags, so you can safely remove those from your <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>
)
}
export default function RootLayout({ children }) {
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
目录的顶层,就会自动添加到应用 <head>
标记中。将 所有支持的文件 移动到 app
目录后,你可以安全地删除其 <link>
标签:
¥Any metadata files such as favicon.ico
, icon.png
, robots.txt
are automatically added to the application <head>
tag as long as you have them placed into the top level of the app
directory. After moving all supported files into the app
directory you can safely delete their <link>
tags:
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>
)
}
export default function RootLayout({ children }) {
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 可以使用 元数据 API 管理你最后的 <head>
标签。将最终元数据信息移至导出的 metadata
对象 中:
¥Finally, Next.js can manage your last <head>
tags with the Metadata API. Move your final metadata info into an exported metadata
object:
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>
)
}
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
通过上述更改,你从在 index.html
中声明所有内容转变为使用框架 (元数据 API) 中内置的 Next.js 基于约定的方法。这种方法使你能够更轻松地提高页面的 SEO 和网络共享性。
¥With the above changes, you shifted from declaring everything in your index.html
to using Next.js' convention-based approach built into the framework (Metadata API). This approach enables you to more easily improve your SEO and web shareability of your pages.
步骤 5:样式
¥Step 5: Styles
与 Create React App 一样,Next.js 内置了对 CSS 模块 的支持。
¥Like Create React App, Next.js has built-in support for CSS Modules.
如果你使用的是全局 CSS 文件,请将其导入到 app/layout.tsx
文件中:
¥If you're using a global CSS file, import it into your app/layout.tsx
file:
import '../index.css'
// ...
如果你使用的是 Tailwind,则需要安装 postcss
和 autoprefixer
:
¥If you're using Tailwind, you'll need to install postcss
and autoprefixer
:
npm install postcss autoprefixer
然后,在项目的根目录下创建一个 postcss.config.js
文件:
¥Then, create a postcss.config.js
file at the root of your project:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
步骤 6:创建入口点页面
¥Step 6: Create the Entrypoint Page
在 Next.js 上,你通过创建 page.tsx
文件来声明应用的入口点。CRA 上与此文件最接近的等效文件是 src/index.tsx
文件。在此步骤中,你将设置应用的入口点。
¥On Next.js you declare an entrypoint for your application by creating a page.tsx
file. The closest equivalent of this file on CRA is your src/index.tsx
file. In this step, you’ll set up the entry point of your application.
在 app
目录中创建 [[...slug]]
目录。
¥Create a [[...slug]]
directory in your app
directory.
由于本指南的目标是首先将 Next.js 设置为 SPA(单页应用),因此你需要页面入口点来捕获应用的所有可能路径。为此,请在 app
目录中创建一个新的 [[...slug]]
目录。
¥Since this guide is aiming to first set up our Next.js as an SPA (Single Page Application), you need your page entry point to catch all possible routes of your application. For that, create a new [[...slug]]
directory in your app
directory.
该目录就是所谓的 可选的包罗万象的路由段。Next.js 使用基于文件系统的路由,其中 目录用于定义路由。这个特殊目录将确保你的应用的所有路由都将定向到其包含的 page.tsx
文件。
¥This directory is what is called an optional catch-all route segment. Next.js uses a file-system based router where directories are used to define routes. This special directory will make sure that all routes of your application will be directed to its containing page.tsx
file.
在 app/[[...slug]]
目录中创建一个新的 page.tsx
文件,内容如下:
¥Create a new page.tsx
file inside the app/[[...slug]]
directory with the following content:
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
该文件是 服务器组件。当你运行 next build
时,该文件将被预渲染为静态资源。它不需要任何动态代码。
¥This file is a Server Component. When you run next build
, the file is prerendered into a static asset. It does not require any dynamic code.
该文件导入我们的全局 CSS 并告诉 generateStaticParams
我们只会生成一条路由,即 /
处的索引路由。
¥This file imports our global CSS and tells generateStaticParams
we are only going to generate one route, the index route at /
.
现在,让我们移动将仅运行客户端的 CRA 应用的其余部分。
¥Now, let's move the rest of our CRA application which will run client-only.
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
该文件是 客户端组件,由 'use client'
指令定义。客户端组件在发送到客户端之前在服务器上仍然是 预渲染为 HTML。
¥This file is a Client Component, defined by the 'use client'
directive. Client Components are still prerendered to HTML on the server before being sent to the client.
由于我们希望启动仅客户端应用,因此我们可以配置 Next.js 以禁用从 App
组件开始的预渲染。
¥Since we want a client-only application to start, we can configure Next.js to disable prerendering from the App
component down.
const App = dynamic(() => import('../../App'), { ssr: false })
现在,更新你的入口点页面以使用新组件:
¥Now, update your entrypoint page to use the new component:
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
步骤 7:更新静态图片导入
¥Step 7: Update Static Image Imports
Next.js 处理静态图片导入的方式与 CRA 略有不同。使用 CRA,导入图片文件将返回其公共 URL 作为字符串:
¥Next.js handles static image imports slightly different from CRA. With CRA, importing an image file will return its public URL as a string:
import image from './img.png'
export default function App() {
return <img src={image} />
}
使用 Next.js,静态图片导入会返回一个对象。然后,该对象可以直接与 Next.js <Image>
组件 一起使用,或者你可以将该对象的 src
属性与现有的 <img>
标记一起使用。
¥With Next.js, static image imports return an object. The object can then be used directly with the Next.js <Image>
component, or you can use the object's src
property with your existing <img>
tag.
<Image>
组件具有 自动图片优化 的附加优点。<Image>
组件根据图片的尺寸自动设置生成的 <img>
的 width
和 height
属性。这可以防止图片加载时布局发生变化。但是,如果你的应用包含的图片仅其中一个尺寸设置了样式,而其他尺寸没有设置为 auto
样式,则这可能会导致问题。当样式未设置为 auto
时,尺寸将默认为 <img>
尺寸属性值,这可能会导致图片出现扭曲。
¥The <Image>
component has the added benefits of automatic image optimization. The <Image>
component automatically sets the width
and height
attributes of the resulting <img>
based on the image's dimensions. This prevents layout shifts when the image loads. However, this can cause issues if your app contains images with only one of their dimensions being styled without the other styled to auto
. When not styled to auto
, the dimension will default to the <img>
dimension attribute's value, which can cause the image to appear distorted.
保留 <img>
标签将减少应用中的更改量并防止上述问题。然后,你可以选择稍后迁移到 <Image>
组件,以利用 配置加载器 优化图片的优势,或者迁移到具有自动图片优化功能的默认 Next.js 服务器。
¥Keeping the <img>
tag will reduce the amount of changes in your application and prevent the above issues. You can then optionally later migrate to the <Image>
component to take advantage of optimizing images by configuring a loader, or moving to the default Next.js server which has automatic image optimization.
将从 /public
导入的图片的绝对导入路径转换为相对导入:
¥Convert absolute import paths for images imported from /public
into relative imports:
// Before
import logo from '/logo.png'
// After
import logo from '../public/logo.png'
将图片 src
属性而不是整个图片对象传递给 <img>
标记:
¥Pass the image src
property instead of the whole image object to your <img>
tag:
// Before
<img src={logo} />
// After
<img src={logo.src} />
或者,你可以根据文件名引用图片资源的公共 URL。例如,public/logo.png
将在 /logo.png
处为你的应用提供图片,这将是 src
值。
¥Alternatively, you can reference the public URL for the image asset based on the filename. For example, public/logo.png
will serve the image at /logo.png
for your application, which would be the src
value.
警告:如果你使用 TypeScript,则在访问
src
属性时可能会遇到类型错误。要修复它们,你需要将next-env.d.ts
添加到tsconfig.json
文件的include
数组 中。当你在步骤 9 上运行应用时,Next.js 将自动生成此文件。¥Warning: If you're using TypeScript, you might encounter type errors when accessing the
src
property. To fix them, you need to addnext-env.d.ts
to theinclude
array of yourtsconfig.json
file. Next.js will automatically generate this file when you run your application on step 9.
步骤 8:迁移环境变量
¥Step 8: Migrate the Environment Variables
Next.js 支持类似于 CRA 的 .env
环境变量。
¥Next.js has support for .env
environment variables similar to CRA.
主要区别在于用于在客户端公开环境变量的前缀。将所有前缀为 REACT_APP_
的环境变量更改为 NEXT_PUBLIC_
。
¥The main difference is the prefix used to expose environment variables on the client-side. Change all environment variables with the REACT_APP_
prefix to NEXT_PUBLIC_
.
步骤 9:更新 package.json
中的脚本
¥Step 9: Update Scripts in package.json
你现在应该能够运行应用来测试是否成功迁移到 Next.js。但在此之前,你需要使用 Next.js 相关命令更新你的 package.json
中的 scripts
,并将 .next
和 next-env.d.ts
添加到你的 .gitignore
文件中:
¥You should now be able to run your application to test if you successfully migrated to Next.js. But before that, you need to update your scripts
in your package.json
with Next.js related commands, and add .next
, and next-env.d.ts
to your .gitignore
file:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ...
.next
next-env.d.ts
现在运行 npm run dev
,并打开 http://localhost:3000
。你应该看到你的应用现在在 Next.js 上运行。
¥Now run npm run dev
, and open http://localhost:3000
. You should see your application now running on Next.js.
步骤 10:清理
¥Step 10: Clean Up
你现在可以从 Create React App 相关工件中清理代码库:
¥You can now clean up your codebase from Create React App related artifacts:
-
删除
public/index.html
¥Delete
public/index.html
-
删除
src/index.tsx
¥Delete
src/index.tsx
-
删除
src/react-app-env.d.ts
¥Delete
src/react-app-env.d.ts
-
删除
reportWebVitals
设置¥Delete
reportWebVitals
setup -
卸载 CRA 依赖 (
react-scripts
)¥Uninstall CRA dependencies (
react-scripts
)
打包器兼容性
¥Bundler Compatibility
Create React App 和 Next.js 都默认使用 webpack 进行打包。
¥Create React App and Next.js both default to using webpack for bundling.
将 CRA 应用迁移到 Next.js 时,你可能有一个要迁移的自定义 Webpack 配置。Next.js 支持提供 自定义 webpack 配置。
¥When migrating your CRA application to Next.js, you might have a custom webpack configuration you're looking to migrate. Next.js supports providing a custom webpack configuration.
此外,Next.js 支持 Turbopack 到 next dev --turbo
,以提高本地开发性能。Turbopack 还支持一些 webpack 加载器,以实现兼容性和增量采用。
¥Further, Next.js has support for Turbopack through next dev --turbo
to improve your local dev performance. Turbopack supports some webpack loaders as well for compatibility and incremental adoption.
下一步
¥Next Steps
如果一切按计划进行,你现在就有了一个作为单页应用运行的正常运行的 Next.js 应用。但是,你尚未利用 Next.js 的大部分优势,但你现在可以开始进行增量更改以获得所有优势。以下是你接下来可能想要执行的操作:
¥If everything went according to plan, you now have a functioning Next.js application running as a single-page application. However, you aren't yet taking advantage of most of Next.js' benefits, but you can now start making incremental changes to reap all the benefits. Here's what you might want to do next:
-
从 React Router 迁移到 Next.js 应用路由 以获得:
¥Migrate from React Router to the Next.js App Router to get:
-
自动代码分割
¥Automatic code splitting
-
很高兴知道:使用
useParams
钩子使用静态导出 目前不支持。¥Good to know: Using a static export does not currently support using the
useParams
hook.