跳至内容
构建您的应用程序升级从 Create React App 迁移

从 Create React App 迁移

本指南将帮助您将现有的 Create React App 站点迁移到 Next.js。

为什么要切换?

您可能希望从 Create React App 切换到 Next.js 的原因有很多。

初始页面加载时间慢

Create React App 纯粹使用客户端 React。仅客户端应用程序(也称为单页应用程序 (SPA))通常会遇到初始页面加载时间慢的问题。这是由于几个原因造成的

  1. 浏览器需要等待 React 代码和您的整个应用程序包下载并运行,然后您的代码才能发送请求以加载数据。
  2. 随着您添加的每个新功能和依赖项,您的应用程序代码都会增长。

没有自动代码分割

之前提到的加载时间慢的问题可以通过代码分割来一定程度地解决。但是,如果您尝试手动进行代码分割,则通常会降低性能。手动代码分割时,很容易无意中引入网络瀑布。Next.js 在其路由器中内置了自动代码分割功能。

网络瀑布

性能不佳的一个常见原因是应用程序进行顺序的客户端-服务器请求以获取数据。SPA 中数据获取的一种常见模式是在初始渲染一个占位符,然后在组件挂载后获取数据。不幸的是,这意味着在父组件完成加载其自身数据之前,获取数据的子组件无法开始获取数据。

虽然 Next.js 支持在客户端获取数据,但它也允许您将数据获取转移到服务器,这可以消除客户端-服务器瀑布。

快速且有意的加载状态

借助对通过 React Suspense 进行流式传输的内置支持,您可以更明确地决定希望首先加载 UI 的哪些部分以及加载顺序,而不会引入网络瀑布。

这使您能够构建加载速度更快的页面并消除布局偏移

选择数据获取策略

根据您的需求,Next.js 允许您在页面和组件的基础上选择数据获取策略。您可以选择在构建时、在服务器端请求时或在客户端获取数据。例如,您可以从您的 CMS 获取数据并在构建时渲染您的博客文章,然后可以将其高效地缓存到 CDN 上。

中间件

Next.js 中间件 允许您在服务器端完成请求之前运行代码。这对于避免在用户访问仅限身份验证的页面时出现未经身份验证的内容闪现(通过将用户重定向到登录页面)特别有用。中间件也适用于实验和 国际化

内置优化

图像字体第三方脚本 通常会对应用程序的性能产生重大影响。Next.js 带有内置组件,可以自动为您优化这些组件。

迁移步骤

我们此次迁移的目标是尽快获得一个可运行的 Next.js 应用程序,以便您可以逐步采用 Next.js 功能。首先,我们将将其保留为纯客户端应用程序(SPA),而不迁移您现有的路由器。这有助于最大程度地减少在迁移过程中遇到问题的可能性,并减少合并冲突。

步骤 1:安装 Next.js 依赖项

您需要做的第一件事是将 next 作为依赖项安装。

终端
npm install next@latest

步骤 2:创建 Next.js 配置文件

在项目的根目录下创建一个 next.config.mjs 文件。此文件将包含您的 Next.js 配置选项

next.config.mjs
/** @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:创建根布局

Next.js App 路由器 应用程序必须包含一个 根布局 文件,它是一个 React 服务器组件,它将包装应用程序中的所有页面。此文件定义在 app 目录的顶层。

CRA 应用程序中与根布局文件最接近的等效文件是 index.html 文件,其中包含您的 <html><head><body> 标记。

在此步骤中,您将把 index.html 文件转换为根布局文件。

  1. 在您的 src 目录中创建一个新的 app 目录。
  2. 在该 app 目录中创建一个新的 layout.tsx 文件。
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

注意:布局文件可以使用 .js.jsx.tsx 扩展名。

index.html 文件的内容复制到前面创建的 <RootLayout> 组件中,同时将 body.div#rootbody.noscript 标记替换为 <div id="root">{children}</div>

app/layout.tsx
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 文件、其他图标(除了 faviconiconapple-icon)和 测试配置,但如果这些是需求,Next.js 也支持这些选项。有关更多信息,请参阅 元数据 API测试 文档。

步骤 4:元数据

Next.js 默认情况下已包含 meta 字符集meta 视口 标记,因此您可以安全地从 <head> 中删除它们。

app/layout.tsx
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.icoicon.pngrobots.txt)只要您将它们放置到 app 目录的顶层,就会自动添加到应用程序的 <head> 标记中。在将 所有受支持的文件 移动到 app 目录后,您可以安全地删除它们的 <link> 标记。

app/layout.tsx
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 可以使用 元数据 API 管理您的最后一个 <head> 标记。将您的最终元数据信息移动到导出的 metadata 对象 中。

app/layout.tsx
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 内置于框架中的基于约定的方法(元数据 API)。这种方法使您能够更轻松地改善页面的 SEO 和网络可共享性。

步骤 5:样式

与 Create React App 一样,Next.js 也内置支持 CSS Modules

如果您使用全局 CSS 文件,请将其导入 app/layout.tsx 文件。

app/layout.tsx
import '../index.css'
 
// ...

如果您使用 Tailwind,则需要安装 postcssautoprefixer

终端
npm install postcss autoprefixer

然后,在项目的根目录下创建一个 postcss.config.js 文件。

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

步骤 6:创建入口点页面

在 Next.js 中,您可以通过创建一个 page.tsx 文件来声明应用程序的入口点。CRA 中与此文件最接近的等效文件是您的 src/index.tsx 文件。在此步骤中,您将设置应用程序的入口点。

在您的 app 目录中创建一个 [[...slug]] 目录。

由于本指南的目标是首先将 Next.js 设置为 SPA(单页应用程序),因此您需要您的页面入口点来捕获应用程序的所有可能路由。为此,在您的 app 目录中创建一个新的 [[...slug]] 目录。

此目录称为 可选通配符路由段。Next.js 使用基于文件系统的路由器,其中 目录用于定义路由。此特殊目录将确保应用程序的所有路由都将定向到其包含的 page.tsx 文件。

app/[[...slug]] 目录中创建一个新的 page.tsx 文件,内容如下。

app/[[...slug]]/page.tsx
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // We'll update this
}

此文件是一个 服务器组件。当您运行 next build 时,该文件会被预渲染成静态资源。它**不需要**任何动态代码。

此文件导入我们的全局 CSS 并告诉 generateStaticParams 我们只打算生成一个路由,即位于 / 处的索引路由。

现在,让我们移动 CRA 应用程序的其余部分,它将仅在客户端运行。

app/[[...slug]]/client.tsx
'use client'
 
import dynamic from 'next/dynamic'
 
const App = dynamic(() => import('../../App'), { ssr: false })
 
export function ClientOnly() {
  return <App />
}

此文件是一个客户端组件,由'use client'指令定义。客户端组件仍然会在发送到客户端之前预渲染为HTML,由服务器处理。

由于我们希望从一开始就构建一个仅限客户端的应用程序,因此我们可以配置Next.js从App组件向下禁用预渲染。

const App = dynamic(() => import('../../App'), { ssr: false })

现在,更新您的入口页面以使用新组件。

app/[[...slug]]/page.tsx
import { ClientOnly } from './client'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return <ClientOnly />
}

步骤 7:更新静态图片导入

Next.js 处理静态图片导入的方式与 CRA 略有不同。在 CRA 中,导入图片文件将返回其公共 URL 作为字符串。

App.tsx
import image from './img.png'
 
export default function App() {
  return <img src={image} />
}

在 Next.js 中,静态图片导入返回一个对象。该对象可以直接与 Next.js 的<Image>组件一起使用,或者您可以使用对象的src属性与您现有的<img>标签一起使用。

<Image>组件具有自动图片优化的额外优势。<Image>组件会根据图片的尺寸自动设置生成的<img>widthheight属性。这可以防止图片加载时出现布局偏移。但是,如果您的应用程序包含仅一个尺寸被设置样式而另一个尺寸未被设置为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 会自动生成此文件。

步骤 8:迁移环境变量

Next.js 支持.env环境变量,类似于 CRA。

主要区别在于用于在客户端公开环境变量的前缀。将所有以REACT_APP_为前缀的环境变量更改为NEXT_PUBLIC_

步骤 9:更新package.json中的脚本

您现在应该能够运行您的应用程序以测试您是否已成功迁移到 Next.js。但在那之前,您需要使用与 Next.js 相关的命令更新package.json中的scripts,并将.nextnext-env.d.ts添加到您的.gitignore文件中。

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "npx serve@latest ./build"
  }
}
.gitignore
# ...
.next
next-env.d.ts

现在运行npm run dev,并打开https://127.0.0.1:3000。您应该会看到您的应用程序现在在 Next.js 上运行。

步骤 10:清理

您现在可以从代码库中清理与 Create React App 相关的工件。

  • 删除public/index.html
  • 删除src/index.tsx
  • 删除src/react-app-env.d.ts
  • 删除reportWebVitals设置。
  • 卸载 CRA 依赖项 (react-scripts)。

打包器兼容性

Create React App 和 Next.js 都默认使用 webpack 进行打包。

在将您的 CRA 应用程序迁移到 Next.js 时,您可能有一个想要迁移的自定义 webpack 配置。Next.js 支持提供自定义 webpack 配置

此外,Next.js 通过next dev --turbo支持Turbopack,以提高您本地开发的性能。Turbopack 还支持一些webpack 加载器以实现兼容性和增量采用。

后续步骤

如果一切按计划进行,您现在将拥有一个作为单页应用程序运行的正常工作的 Next.js 应用程序。但是,您还没有利用 Next.js 的大部分优势,但您现在可以开始进行增量更改以获得所有优势。以下是一些您接下来可能想要做的事情。

需知:使用静态导出目前不支持使用useParams钩子。