跳到內容
建置你的應用程式升級從 Create React App 遷移

從 Create React App 遷移

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

為何切換?

您可能想要從 Create React App 切換到 Next.js 的幾個原因

初始頁面載入時間過慢

Create React App 純粹使用客戶端 React。純客戶端應用程式,也稱為單頁應用程式 (SPA),通常會遇到初始頁面載入時間過慢的問題。發生這種情況有幾個原因

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

沒有自動程式碼分割

程式碼分割可以在某種程度上緩解先前載入時間過慢的問題。但是,如果您嘗試手動進行程式碼分割,您可能會不經意地引入網路瀑布流。Next.js 提供自動程式碼分割和 tree-shaking,這些功能內建於其路由器和建置管線中。

網路瀑布流

當應用程式發出循序客戶端-伺服器請求以提取資料時,就會發生效能不佳的常見原因。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 設定選項

next.config.ts
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> 標籤。

  1. 在您的 src 目錄中 (如果您希望 app 位於根目錄,則在您的專案根目錄中) 建立新的 app 目錄。
  2. app 目錄中,建立 layout.tsx (或 layout.js) 檔案
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

現在將舊的 index.html 的內容複製到此 <RootLayout> 元件中。將 body div#root (和 body 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、其他圖示和測試設定。如果您需要這些,Next.js 透過其Metadata API測試設定提供支援。

步驟 4:元數據

Next.js 自動包含 <meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /> 標籤,因此您可以從 <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 可以使用 Metadata 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 內建於框架中的基於慣例的方法 (Metadata API)。這種方法使您能夠更輕鬆地改進頁面的 SEO 和 Web 分享性。

步驟 5:樣式

與 CRA 一樣,Next.js 開箱即用支援 CSS Modules。它也支援全域 CSS 導入

如果您有全域 CSS 檔案,請將其導入到您的 app/layout.tsx

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 並攔截所有路由,因此我們將使用選用捕獲所有路由

  1. app 內建立 [[...slug]] 目錄。
app
  [[...slug]]
   page.tsx
  layout.tsx
  1. 將以下內容新增至 page.tsx:
app/[[...slug]]/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)

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' 指令使此檔案成為客戶端元件
  • 具有 ssr: falsedynamic 導入會停用 <App /> 元件的伺服器端渲染,使其成為真正的僅限客戶端 (SPA)。

現在更新您的 page.tsx (或 page.js) 以使用您的新元件

app/[[...slug]]/page.tsx
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>widthheight 屬性。這可以防止圖片載入時發生版面配置偏移。但是,如果您的應用程式包含僅設定其中一個維度樣式而另一個維度未設定為 auto 的圖片,則可能會導致問題。當未設定為 auto 時,維度將預設為 <img> dimension 屬性的值,這可能會導致圖片看起來失真。

保留 <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 命令。此外,將 .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

開啟 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 中複製該設定

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.json 中使用 proxy 欄位將請求轉發到後端伺服器,您可以使用 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 }) => {
    // Modify the webpack config here
    return config
  },
}
 
export default nextConfig

注意:這將需要停用 Turbopack,方法是從您的 dev 指令碼中移除 --turbopack

TypeScript 設定

如果您有 tsconfig.json,Next.js 會自動設定 TypeScript。請確保 next-env.d.ts 列在您的 tsconfig.json include 陣列中

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

Bundler 相容性

Create React App 和 Next.js 預設都使用 webpack 進行捆綁。Next.js 也提供 Turbopack,以便更快速地進行本機開發

next dev --turbopack

如果您需要從 CRA 遷移進階 webpack 設定,您仍然可以提供自訂 webpack 設定

後續步驟

如果一切運作正常,您現在應該有一個作為單頁應用程式執行的 Next.js 應用程式。您尚未充分利用伺服器端渲染或基於檔案路由等 Next.js 功能,但您現在可以逐步執行這些操作

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