Next 13 App Router 在 Server Component 取得目前的 pathname 與 searchParams

前言

目前 Server Component 尚未提供可以直接取得 pathname 與 SearchParams 的方式
僅能從最上層的 Page 參數取得
若希望在比較底層的組件可以無腦用,而不是一直由 Page 傳下來的話

現在只有下列解法

轉成 Client 組件,使用 hook usePathname, useSearchParams

是官方推薦也是比較正規的解法
考慮 SEO 問題的話,可以參考本站文章: ‘use client’ 與 SEO 問題

這裡節錄重點: use client 相當於 Page Router 的 getServerSideProps。是 SSR 後再到 Client hydration。仍可保有 SEO

使用 header() 取得

使用 x-invoke-query, x-invoke-path

const header = headers();
const activePath = header.get('x-invoke-path'); // 網址
const query = header.get('x-invoke-query'); // queryString

但根據官方 issue 討論串
部署在 vercel 的話,是拿不到x-invoke-query, x-invoke-path的;本機開發則是一定可以取得

這解法最簡單粗暴,但需要部署後確認平台能否正確取得

使用 header() + middleware

  1. 增加 middleware.ts,在 request 進來時在 header 設上 pathname 與 searchParams
// /middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request: Request) {

  // Store current request url in a custom header, which you can read later
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-url', request.url);

  return NextResponse.next({
    request: {
      // Apply new request headers
      headers: requestHeaders,
    }
  });
}
  1. 在組件裡由 header 取得
// /app/layout.tsx
import { headers } from 'next/headers';

export default function RootLayout() {
  const headersList = headers();
  // read the custom x-url header
  const pathname = headersList.get('x-url') || '';
}

功比較多,但也最穩健

後記

我自己最後選擇第一個轉成 Client 操作
我們的需求是要將 utm 相關資訊都帶進頁面上所有連結裡

同時考量這 Link 組件也會在 Client 使用,並不是只有在 Server 需要處理

裡面還多了 20 多行 js 做判斷
當沒有 searchParams 時,這段 js 第一行就 return 掉了

去 pageSpeed 測試速度,帶 utm 參數額外處理,跑分還比沒帶 utm 的高= =
若真的很介意,希望能在 Server 處理掉所有事的話
那就推薦第 2、3 個解法,若不是部署在 vercel,說不定用 2 就可以輕鬆解決問題了!

最差情況,就第 3 招強塞參數來處理

參考資料

GitHub / [Next 13] Server Component + Layout.tsx - Can’t access the URL / Pathname