跳至内容
构建您的应用程序升级从 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> 组件中,同时用 <div id="root">{children}</div> 替换 body.div#rootbody.noscript 标签。

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 钩子。