Mark Ku's Blog
首頁 關於我
NEXTJS 13.3.4 昇級踩坑筆記,Server side component 時代來臨 - migrate page route to app route
Frontend
NEXTJS 13.3.4 昇級踩坑筆記,Server side component 時代來臨 - migrate page route to app route
Mark Ku
Mark Ku
May 27, 2023
1 min

前言

為了重寫我們的德國電商網站,我進行了深入評估,並決定採用 Next js 的最新穩定版本13.4.3,這個版本的 SEO meta api和server side component功能具有極高的吸引力,且加上未來許多 NextJS的功能都將以app route 上,因此我們選擇採用app route對我們來說毫無疑問。

1 Server side component

1.1 App folder 下的元件或頁面,預設都是 server side component

  • 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’:

1.2 Server side component 好處

Server Components的運作方式是在伺服器上進行渲染,從而只傳遞需要的JS Bundle程式碼至用戶端瀏覽器,不必要的js則不會被下載,這個特性能有效地減少網路傳輸量,提高效能和速度。

1.3限制

不能使用 useState 和 useReducer、 Hooks、useEffect、useLayoutEffect

1.4 如果要使用 clinet side component ,每個 component 都要加上這一行

"use client";
import xxx
...

2 新 useRoute

import { useRouter, useParams, usePathname ,useSearchParams } from 'next/navigation';

2.1 在新的 useRoute ,並沒有 locale

const { locale } = useRouter(); 

2.2 路由

2.2.1 資料夾結構

這種改動的方式,更直觀從資料夾結構了解是頁面還是元件。

UrlPage routeApp 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

2.2.2 api 路由

PageRoute 2Result 3
app/page.jsapp/route.jsConflict
app/page.jsapp/api/route.jsValid
app/[user]/page.jsapp/api/route.jsValid
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 });
}

2.3 Data Fetching 方式調整

2.3.1 新的寫法並不透過 getStaticProps、getServerSideProp 去要資料

async func getData() {
  const res = await fetch ("https://api.xxx.com/...");
  return res.json();
}

export default async function About() {
  const name = await getData();
  return "...";
}

2.3.2 新增 use hook 可以將非同步的結果回傳回來

use(getData(id))

2.3.3 server side 改寫了元生的 fetch api ,並預設有cache 機制,且官方並不建議包裝fetch 在client component

2.3.4 revalidate

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

3. 提供 meta api 替代 next/head,更容易的針對每個頁面給不同 seo 的 meta data

3.1 Static Metadata

import { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
};
 
export default function Page() {}

3.2 Dynamic Metadata

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) {}

4 昇級後受到影響的套件

4.1 i18n 套件 ( 推薦 )

剛昇級完後就會發現相關的 i18n 套件無法使用,新版 useRouter 也不提供 locale 可以使用,試了好幾個 i18n 套件,發現這個套件最好用

相關文章

import { useLocale } from 'next-intl';

const locale = useLocale();

4.2 react query 注入方式

相關文章

4.4 context api 載入方式

相關文章

5 Turbopack( 仍在 Beta )

最後可以持續觀注的,Next.js 13 加了一個名為 Turbopack 的新的 JavaScript 打包工具,它被稱為 Webpack 的繼承者,Turbopack 由 Webpack 由 Rust 撰寫,號稱比原始 Webpack 快 700 倍(並且比 Vite 快 10 倍)。

6. 結論

無可否認,Next js 的更新速度令人驚訝,經常在我醒來之後就有了新的穩定版本,每次改版都會有點小陣痛,但大概都花個一至兩天就能昇級,也保留舊的寫法。

app route 改動的幅度有點大,且有點痛,但 app route出現,正式掀開 server side component 的序幕。

附錄 - 一些快速昇級的 powershell

1.寫了一個 powershell 快速將 page folder 搬到 app folder

# 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

2.用 powershell 快速將元件轉成 client side 元件的

# 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

3. 用Powershell 遍歷資料夾下的Json 合併成在一個新的 JSON 檔案

# 初始化空的 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"


Tags

Mark Ku

Mark Ku

Software Developer

8年以上豐富網站開發經驗,直播系統、POS系統、電子商務、平台網站、SEO、金流串接、DevOps、Infra 出身,帶過幾次團隊,目前專注於北美及德國市場電商網站開發團隊。

Expertise

前端(React)
後端(C#)
網路管理
DevOps
溝通
領導

Social Media

facebook github website

Related Posts

程式碼標準 Coding Standard
程式碼標準 Coding Standard
March 22, 2023
1 min

Quick Links

關於我

Social Media