如何從 Create React App 遷移至 Next.js

本指南將協助您將現有的 Create React App (CRA) 網站遷移至 Next.js。

為什麼要轉換?

從 Create React App 轉換至 Next.js 有以下幾個原因:

初始頁面載入時間緩慢

Create React App 僅使用客戶端 React。純客戶端應用程式(也稱為單頁應用程式 (SPAs))通常會遇到初始頁面載入時間緩慢的問題。這是由以下幾個原因造成的:

  1. 瀏覽器需要等待 React 程式碼和整個應用程式套件下載並執行後,您的程式碼才能發送請求載入資料。
  2. 隨著新增功能和依賴項,您的應用程式程式碼會不斷增長。

缺乏自動程式碼分割

上述載入時間緩慢的問題可以透過程式碼分割稍作緩解。但如果您嘗試手動進行程式碼分割,可能會無意中引入網路瀑布效應。Next.js 在其路由器和建置流程中內建了自動程式碼分割和樹搖 (tree-shaking) 功能。

網路瀑布效應

效能不佳的一個常見原因是應用程式為了獲取資料而進行連續的客戶端-伺服器請求。SPA 中資料獲取的一種模式是先渲染佔位符,然後在元件掛載後獲取資料。不幸的是,子元件只能在父元件完成載入自己的資料後才能開始獲取資料,從而導致請求的「瀑布」效應。

雖然 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 配置選項

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'export', // 輸出單頁應用程式 (SPA)
  distDir: 'build', // 將建置輸出目錄更改為 `build`
}

export default nextConfig

注意:使用 output: 'export' 表示您正在進行靜態匯出。您將無法存取伺服器端功能,如 SSR 或 API。您可以移除這一行以利用 Next.js 的伺服器功能。

步驟 3:建立根佈局

Next.js App Router 應用程式必須包含一個 根佈局 檔案,這是一個 React 伺服器元件,將包裹所有頁面。

在 CRA 應用程式中,最接近根佈局檔案的是 public/index.html,其中包含您的 <html><head><body> 標籤。

  1. 在您的 src 資料夾內建立一個新的 app 目錄(或者如果您希望 app 位於根目錄,則在專案根目錄中建立)。
  2. 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 提供了 Metadata 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.icoicon.pngrobots.txt,只要將它們放置在 app 目錄的頂層,就會自動添加到應用程式的 <head> 標籤中。將 所有支援的檔案 移至 app 目錄後,您可以安全地刪除它們的 <link> 標籤:

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 管理您的最後 <head> 標籤。將您的最終元資料資訊移至匯出的 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 和網路分享性。

步驟 5:樣式

與 CRA 一樣,Next.js 開箱即用地支援 CSS 模組。它還支援 全域 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 並攔截所有路由,我們將使用 可選的 catch-all 路由

  1. app 內建立一個 [[...slug]] 目錄。
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. 將以下內容添加到 page.tsx
export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // 我們稍後會更新此處
}

這告訴 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: falsedynamic 導入會禁用 <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> 元件具有自動圖片最佳化的額外優勢。該元件會根據圖片尺寸自動設定結果 <img>widthheight 屬性,防止圖片載入時發生版面位移。但若應用程式中的圖片僅設定單一維度樣式而未將另一維度設為 auto,可能導致問題。未設為 auto 時,維度會預設為 <img> 維度屬性的值,可能造成圖片顯示變形。

保留 <img> 標籤可減少應用程式變更量並避免上述問題。之後可選擇性遷移至 <Image> 元件,透過設定 loader 或改用預設 Next.js 伺服器(具備自動圖片最佳化功能)來最佳化圖片。

將從 /public 匯入圖片的絕對路徑轉為相對路徑:

// 之前
import logo from '/logo.png'

// 之後
import logo from '../public/logo.png'

將圖片的 src 屬性(而非整個圖片物件)傳遞給 <img> 標籤:

// 之前
<img src={logo} />

// 之後
<img src={logo.src} />

或者,可根據檔案名稱參照圖片的公開 URL。例如,public/logo.png 會將圖片提供在應用程式的 /logo.png 路徑,此即為 src 值。

警告: 若使用 TypeScript,存取 src 屬性時可能遇到型別錯誤。需將 next-env.d.ts 加入 tsconfig.json 檔案的 include 陣列來解決。Next.js 會在步驟 9 執行應用程式時自動產生此檔案。

步驟 9:遷移環境變數

Next.js 的環境變數支援與 CRA 類似,但必須為需在瀏覽器公開的變數加上 NEXT_PUBLIC_ 前綴。

主要差異在於用戶端公開環境變數的前綴。將所有 REACT_APP_ 前綴的環境變數改為 NEXT_PUBLIC_

步驟 10:更新 package.json 中的指令

更新 package.json 的指令以使用 Next.js 命令,並將 .nextnext-env.d.ts 加入 .gitignore

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

現在可執行:

npm run dev

開啟 http://localhost: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.js 中使用 basePath 設定來達成:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  basePath: '/my-subpath',
  // ...
}

export default nextConfig

處理自訂 Service Worker

若曾使用 CRA 的 service worker(如 create-react-appserviceWorker.js),可學習如何用 Next.js 建立漸進式網路應用程式 (PWA)

代理 API 請求

若 CRA 應用程式曾使用 package.jsonproxy 欄位轉發請求至後端伺服器,可在 next.config.ts 中使用 Next.js rewrites 達成:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://your-backend.com/:path*',
      },
    ]
  },
}

自訂 Webpack / Babel 設定

若曾在 CRA 中有自訂 webpack 或 Babel 設定,可在 next.config.ts 擴充 Next.js 設定:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    // 在此修改 webpack 設定
    return config
  },
}

export default nextConfig

注意: 這需要從 dev 指令移除 --turbopack 來停用 Turbopack。

TypeScript 設定

Next.js 在存在 tsconfig.json 時會自動設定 TypeScript。確認 next-env.d.ts 已列在 tsconfig.jsoninclude 陣列中:

{
  "include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}

打包工具相容性

Create React App 和 Next.js 預設都使用 webpack 進行打包。Next.js 也提供 Turbopack 以加速本地開發:

next dev --turbopack

若需從 CRA 遷移進階 webpack 設定,仍可提供自訂 webpack 設定

後續步驟

若一切順利,現在已有一個以單頁應用程式形式運行的 Next.js 應用程式。雖然尚未利用伺服器渲染或檔案式路由等 Next.js 功能,但可逐步實作:

注意: 使用靜態匯出 (output: 'export') 目前不支援 useParams 掛鉤或其他伺服器功能。若要使用所有 Next.js 功能,請從 next.config.ts 移除 output: 'export'