為了重寫我們的德國電商網站,我進行了深入評估,並決定採用 Next js 的最新穩定版本13.4.3,這個版本的 SEO meta api和server side component功能具有極高的吸引力,且加上未來許多 NextJS的功能都將以app route 上,因此我們選擇採用app route對我們來說毫無疑問。
By default, all components on NextJS 13 inside the App folder are server components. And Server Components cannot use client features such as useState, useEffect, etc.
For now, to use third-party components the solution is to create a wrapper for each client component that doesn’t include the directive ‘use client’:
Server Components的運作方式是在伺服器上進行渲染,從而只傳遞需要的JS Bundle程式碼至用戶端瀏覽器,不必要的js則不會被下載,這個特性能有效地減少網路傳輸量,提高效能和速度。
不能使用 useState 和 useReducer、 Hooks、useEffect、useLayoutEffect
"use client"; import xxx ...
import { useRouter, useParams, usePathname ,useSearchParams } from 'next/navigation';
const { locale } = useRouter();
這種改動的方式,更直觀從資料夾結構了解是頁面還是元件。
Url | Page route | App route |
---|---|---|
/ | /page/index.tsx | /app/page.tsx |
/about-us | /page/about-us.tsx | /app/about-us/page.tsx |
P.S. 附錄有寫了一個Powershell 快速將 page folder 遷移到 app folder
Page | Route 2 | Result 3 |
---|---|---|
app/page.js | app/route.js | Conflict |
app/page.js | app/api/route.js | Valid |
app/[user]/page.js | app/api/route.js | Valid |
app/products/api/route.ts import { NextResponse } from 'next/server'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id'); const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY, }, }); const product = await res.json(); return NextResponse.json({ product }); }
async func getData() { const res = await fetch ("https://api.xxx.com/..."); return res.json(); } export default async function About() { const name = await getData(); return "..."; }
use(getData(id))
SSG:頁面預設就是 SSG
SSR:在元件宣告為非同步,且請求時必須關閉緩存,並在請求參數中的cache欄位設置為 no-store、no-cache 或者設置revalidate 為 0 的時候,才會是動態服務端渲染。
CSR:在使用 “use client” 的客戶端元件中,在Use Effect 後請用的渲染的元件
ISR:在 fetch 請求中設置 revalidate,或者在 page.tsx 宣告 revalidate。
app/page.tsx export const revalidate = 60; // revalidate this page every 60 seconds
P.S. 過去的 pages/api/revalidate 的web hook 仍然有效,但要在page fodler
import { Metadata } from 'next'; export const metadata: Metadata = { title: '...', description: '...', }; export default function Page() {}
import { Metadata, ResolvingMetadata } from 'next'; type Props = { params: { id: string }; searchParams: { [key: string]: string | string[] | undefined }; }; export async function generateMetadata( { params, searchParams }: Props, parent?: ResolvingMetadata, ): Promise<Metadata> { // read route params const id = params.id; // fetch data const product = await fetch(`https://.../${id}`).then((res) => res.json()); // optionally access and extend (rather than replace) parent metadata const previousImages = (await parent).openGraph?.images || []; return { title: product.title, openGraph: { images: ['/some-specific-page-image.jpg', ...previousImages], }, }; } export default function Page({ params, searchParams }: Props) {}
剛昇級完後就會發現相關的 i18n 套件無法使用,新版 useRouter 也不提供 locale 可以使用,試了好幾個 i18n 套件,發現這個套件最好用
import { useLocale } from 'next-intl'; const locale = useLocale();
最後可以持續觀注的,Next.js 13 加了一個名為 Turbopack 的新的 JavaScript 打包工具,它被稱為 Webpack 的繼承者,Turbopack 由 Webpack 由 Rust 撰寫,號稱比原始 Webpack 快 700 倍(並且比 Vite 快 10 倍)。
無可否認,Next js 的更新速度令人驚訝,經常在我醒來之後就有了新的穩定版本,每次改版都會有點小陣痛,但大概都花個一至兩天就能昇級,也保留舊的寫法。
app route 改動的幅度有點大,且有點痛,但 app route出現,正式掀開 server side component 的序幕。
# Get a reference to all tsx files in the src\pages directory. $files = Get-ChildItem -Path "src\pages" -Filter "*.tsx" # For each file, create a new directory in src\app with the same name as the file (without extension), # move the file to the new directory, rename it to page.tsx, and prepend 'use client;' to it. foreach ($file in $files) { # Create new directory. $newDir = New-Item -Path "src\app\$($file.BaseName)\" -ItemType Directory # Move and rename the file. $newFile = Move-Item -Path $file.FullName -Destination "$($newDir.FullName)\page.tsx" -PassThru # Add 'use client;' to the beginning of the file. $content = Get-Content -Path $newFile.FullName $newContent = 'use client;' + "`n" + $content Set-Content -Path $newFile.FullName -Value $newContent } pause
# Get a reference to all tsx files in the src\pages directory. $files = Get-ChildItem -Path "src\pages" -Filter "*.tsx" # For each file, create a new directory in src\app with the same name as the file (without extension), # move the file to the new directory, rename it to page.tsx, and prepend 'use client;' to it. foreach ($file in $files) { # Create new directory. $newDir = New-Item -Path "src\app\$($file.BaseName)\" -ItemType Directory # Move and rename the file. $newFile = Move-Item -Path $file.FullName -Destination "$($newDir.FullName)\page.tsx" -PassThru # Add 'use client;' to the beginning of the file. $content = Get-Content -Path $newFile.FullName $newContent = 'use client;' + "`n" + $content Set-Content -Path $newFile.FullName -Value $newContent } pause
# 初始化空的 Hashtable $combinedJson = @{} # 指定資料夾路徑 $folderPath = 'C:\path\to\json\files' # 尋找所有 .json 檔案 Get-ChildItem -Path $folderPath -Filter *.json | ForEach-Object { # 獲取檔案名稱並移除 .json 副檔名 $propertyName = $_.BaseName # 讀取 JSON 檔案內容 $content = Get-Content $_.FullName | ConvertFrom-Json # 將 JSON 內容加入到 combinedJson 中,使用檔案名稱作為屬性 $combinedJson[$propertyName] = $content } # 將合併的 JSON 轉換為字符串並寫入到一個新的 JSON 檔案 $combinedJson | ConvertTo-Json | Set-Content -Path "$folderPath\combined.json"