從 Vite 遷移
本指南將協助您將現有的 Vite 應用程式遷移至 Next.js。
為何要轉換?
您可能有幾個理由想要從 Vite 轉換到 Next.js。
初始頁面載入時間緩慢
如果您使用預設的 Vite React 插件建構應用程式,您的應用程式將會是一個純客戶端應用程式。僅限客戶端的應用程式,也稱為單頁應用程式 (SPA),通常初始頁面載入時間較慢。發生這種情況的原因有幾個:
- 瀏覽器需要等待 React 程式碼和整個應用程式套件下載並執行後,您的程式碼才能發送請求以載入一些資料。
- 您的應用程式程式碼會隨著您新增的每個新功能和額外依賴項而增長。
沒有自動程式碼分割
前述載入時間緩慢的問題可以透過程式碼分割來略微改善。然而,如果您嘗試手動進行程式碼分割,通常會使效能變得更糟。手動進行程式碼分割時,很容易無意間引入網路瀑布效應。Next.js 在其路由器中內建了自動程式碼分割功能。
網路瀑布圖
應用程式效能低落的一個常見原因是,應用程式會發出連續的客戶端-伺服器請求來擷取資料。SPA 中一個常見的資料擷取模式是先渲染一個佔位符,然後在組件掛載後再擷取資料。遺憾的是,這表示擷取資料的子組件必須等到父組件載入完自己的資料後才能開始擷取。
雖然 Next.js 支援在客戶端擷取資料,但它也提供將資料擷取轉移到伺服器的選項,這可以消除客戶端-伺服器瀑布圖。
快速且可控制的載入狀態
透過內建的 React Suspense 串流支援,您可以更精確地控制要先載入 UI 的哪些部分以及載入順序,而不會造成網路瀑布圖。
這讓您可以建立載入速度更快的頁面,並消除版面配置偏移。
選擇資料擷取策略
根據您的需求,Next.js 允許您在頁面和組件的基礎上選擇資料擷取策略。您可以決定在建置時、伺服器上的請求時或在客戶端上擷取資料。例如,您可以從 CMS 擷取資料並在建置時渲染您的部落格文章,然後可以有效地在 CDN 上快取這些文章。
中介軟體允許您在請求完成之前在伺服器上執行程式碼。當使用者造訪僅限驗證的頁面時,這對於避免出現未經驗證內容的閃爍特別有用,它會將使用者重新導向到登入頁面。該中介軟體也可用於實驗和國際化。
內建優化
圖片、字體 和 第三方腳本 通常會對應用程式的效能產生顯著影響。Next.js 內建了可自動為您優化這些資源的元件。
遷移步驟
步驟 2:建立 Next.js 設定檔 Next.js 設定選項 。
next.config.mjs/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
}
export default nextConfig
注意事項: 您的 Next.js 設定檔可以使用 .js
或 .mjs
副檔名。
步驟 3:更新 TypeScript 設定 專案參考
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
}
export default nextConfig
注意事項: 您的 Next.js 設定檔可以使用 .js
或 .mjs
副檔名。
./dist/types/**/*.ts
和 ./next-env.d.ts
加入到 include
陣列./node_modules
加入到 exclude
陣列{ "name": "next" }
加入到 compilerOptions
中的 plugins
陣列"plugins": [{ "name": "next" }]
esModuleInterop
true
:"esModuleInterop": true
jsx
preserve
:"jsx": "preserve"
allowJs
true
:"allowJs": true
forceConsistentCasingInFileNames
true
:"forceConsistentCasingInFileNames": true
incremental
true
:"incremental": true
以下是一個包含這些變更的有效 tsconfig.json
範例
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
"exclude": ["./node_modules"]
}
您可以在Next.js 文件中找到更多關於設定 TypeScript 的資訊。
步驟 4:建立根佈局
Next.js 的 App Router 應用程式必須包含一個 根佈局 檔案,它是一個 React 伺服器元件,會包裹應用程式中的所有頁面。這個檔案定義在 app
目錄的最上層。
在 Vite 應用程式中最接近根佈局檔案的等效物是 index.html
檔案<html>
、<head>
和 <body>
標籤。
在此步驟中,您會將 index.html
檔案轉換為根佈局檔案。
- 在
src
目錄中建立一個新的app
目錄。 - 在該
app
目錄內建立一個新的layout.tsx
檔案。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
注意事項:佈局檔案可以使用
.js
、.jsx
或.tsx
副檔名。
- 將
index.html
檔案的內容複製到先前建立的<RootLayout>
元件中,同時將body.div#root
和body.script
標籤替換為<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" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
- Next.js 預設已包含 meta charset
和 meta viewport 標籤,因此您可以安全地從<head>
中移除它們。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
- 任何 中繼資料檔案,例如
favicon.ico
、icon.png
、robots.txt
,只要您將它們放置在app
目錄的最上層,就會自動新增到應用程式的<head>
標籤中。將 所有支援的檔案 移至app
目錄後,您可以安全地刪除它們的<link>
標籤。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>My App</title>
<meta name="description" content="My App is a..." />
</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: 'My App',
description: 'My App is a...',
}
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:建立入口頁面
在 Next.js 中,您透過建立 page.tsx
檔案來宣告應用程式的入口點。在 Vite 中,與此檔案最接近的對應檔案是您的 main.tsx
檔案。在此步驟中,您將設定應用程式的入口點。
- 在您的
app
目錄中建立一個[[...slug]]
目錄。
由於在本指南中,我們首先的目標是將 Next.js 設定為 SPA(單頁應用程式),因此您的頁面入口點需要捕捉應用程式所有可能的路由。為此,請在您的 app
目錄中建立一個新的 [[...slug]]
目錄。
這個目錄就是所謂的可選的全域捕捉路由區段。Next.js 使用基於檔案系統的路由器,其中目錄用於定義路由。這個特殊的目錄將確保應用程式的所有路由都會導向到其包含的 page.tsx
檔案。
- 在
app/[[...slug]]
目錄內建立一個新的page.tsx
檔案,並包含以下內容
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
注意事項:頁面檔案可以使用
.js
、.jsx
或.tsx
副檔名。
這個檔案是一個伺服器元件。當您執行 next build
時,該檔案會被預渲染成靜態資源。它*不需要*任何動態程式碼。
這個檔案匯入了我們的全域 CSS,並告知generateStaticParams
我們只會產生一個路由,即位於 /
的索引路由。
現在,讓我們移動 Vite 應用程式的其餘部分,這些部分將僅在客戶端執行。
'use client'
import React from 'react'
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 })
現在,更新您的入口頁面以使用新的元件
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
步驟 6:更新靜態圖片匯入
Next.js 處理靜態圖片匯入的方式與 Vite 略有不同。使用 Vite 時,匯入圖片檔會以字串形式返回其公開 URL
import image from './img.png' // `image` will be '/assets/img.2d8efhg.png' in production
export default function App() {
return <img src={image} />
}
在 Next.js 中,靜態圖片導入會返回一個物件。該物件可以直接與 Next.js 的 <Image>
元件 一起使用,或者您可以將物件的 src
屬性與現有的 <img>
標籤一起使用。
<Image>
元件具有 自動圖片優化 的額外優勢。<Image>
元件會根據圖片的尺寸自動設定結果 <img>
的 width
和 height
屬性。這可以防止圖片載入時版面偏移。但是,如果您的應用程式包含僅其中一個維度設定樣式而另一個維度未設定為 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
屬性時可能會遇到類型錯誤。您現在可以安全地忽略這些錯誤。在本指南結束前,它們將會被修復。
步驟 7:遷移環境變數
Next.js 支援 .env
環境變數,類似於 Vite。主要區別在於用於在用戶端公開環境變數的前綴。
- 將所有帶有
VITE_
前綴的環境變數更改為NEXT_PUBLIC_
。
Vite 在特殊的 import.meta.env
物件上公開了一些內建的環境變數,這些變數不受 Next.js 支援。您需要如下更新它們的用法
import.meta.env.MODE
⇒process.env.NODE_ENV
import.meta.env.PROD
⇒process.env.NODE_ENV === 'production'
import.meta.env.DEV
⇒process.env.NODE_ENV !== 'production'
import.meta.env.SSR
⇒typeof window !== 'undefined'
Next.js 也不提供內建的 BASE_URL
環境變數。但是,如果需要,您仍然可以設定一個
- 將以下內容添加到您的
.env
檔案
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
- 在您的
next.config.mjs
檔案中,將basePath
設定為process.env.NEXT_PUBLIC_BASE_PATH
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
basePath: process.env.NEXT_PUBLIC_BASE_PATH, // Sets the base path to `/some-base-path`.
}
export default nextConfig
- 將
import.meta.env.BASE_URL
的用法更新為process.env.NEXT_PUBLIC_BASE_PATH
步驟 8:更新 package.json
中的 Scripts
現在您應該可以執行應用程式來測試是否成功遷移到 Next.js。但在這之前,您需要使用 Next.js 相關指令更新 package.json
中的 scripts
,並將 .next
和 next-env.d.ts
新增到 .gitignore
中。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
# ...
.next
next-env.d.ts
dist
現在執行 npm run dev
,並開啟 https://127.0.0.1:3000
範例:查看 這個 Pull Request
以取得 Vite 應用程式遷移到 Next.js 的實際範例。
步驟 9:清理
您現在可以從程式碼庫中清除 Vite 相關的產物。
- 刪除
main.tsx
- 刪除
index.html
- 刪除
vite-env.d.ts
- 刪除
tsconfig.node.json
- 刪除
vite.config.ts
- 解除安裝 Vite dependencies
後續步驟
如果一切按計劃進行,您現在擁有一個可以作為單頁應用程式運行的 Next.js 應用程式。然而,您尚未充分利用 Next.js 的大部分優勢,但您現在可以開始進行漸進式更改以獲得所有優勢。以下是您接下來可能想做的事情:
- 從 React Router 遷移到 Next.js App Router 以獲得:
- 自動程式碼分割
- 串流伺服器渲染
- React 伺服器元件
- 使用
<Image>
元件最佳化圖片 - 使用
next/font
最佳化字型 - 使用
<Script>
元件最佳化第三方腳本 - 更新您的 ESLint 設定以支援 Next.js 規則