跳到內容

從 Create React App 遷移

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

為什麼要切換?

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

初始頁面載入時間緩慢

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

  1. 瀏覽器需要等待 React 程式碼和您的整個應用程式 bundle 下載並執行,之後您的程式碼才能發送請求以載入資料。
  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 APITesting 設定提供支援。

步驟 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 和網路分享性。

步驟 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 並攔截所有路由,因此我們將使用可選的 catch-all 路由

  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 中,匯入圖片檔案會將其 public 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> 尺寸屬性的值,這可能會導致圖片看起來變形。

保留 <img> 標籤將減少應用程式中的變更量,並防止上述問題。然後,您可以選擇稍後遷移到 <Image> 元件,以透過設定 loader 或遷移到具有自動圖片優化的預設 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} />

或者,您可以根據檔案名稱參考圖片資源的 public 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://127.0.0.1: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 建立 漸進式 Web 應用程式 (PWA)

代理 API 請求

如果您的 CRA 應用程式在 package.json 中使用 proxy 欄位,將請求轉發到後端伺服器,您可以使用 next.config.ts 中的 Next.js 重新導向來複製此行為。

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

注意:這將需要透過從您的 dev 指令碼中移除 --turbopack 來停用 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'