App Router の Server Component で URL を取得する方法
まとめ
Next.js の App router の Server Component で URL を取得できます。
以下が実装手順です。
- middleware で URL を取得し、リクエストヘッダーに
x-url
を追加 - Server Component でリクエストヘッダーの
x-url
から URL を取得
以下の例では取得した URL と URL クエリパラメターを画面に表示しています。
Next.jsプロジェクトの作成
動作を作業するための Next.js プロジェクトを作成します。長いので、折り畳んでおきます。
新規プロジェクト作成と初期環境構築の手順詳細
プロジェクトを作成
create next-app@latest
でプロジェクトを作成します。
$ pnpm create next-app@latest next-approuter-servercomponent-fullurl --typescript --eslint --import-alias "@/*" --src-dir --use-pnpm --tailwind --app
$ cd next-approuter-servercomponent-fullurl
Peer Dependenciesの警告を解消
Peer dependenciesの警告が出ている場合は、pnpm install
を実行し、警告を解消します。
WARN Issues with peer dependencies found
.
├─┬ eslint-config-next 14.2.1
│ └─┬ @typescript-eslint/parser 7.2.0
│ └── ✕ unmet peer eslint@^8.56.0: found 8.0.0
└─┬ next 14.2.1
├── ✕ unmet peer react@^18.2.0: found 18.0.0
└── ✕ unmet peer react-dom@^18.2.0: found 18.0.0
以下を実行することで警告が解消されます。
$ pnpm i -D eslint@^8.56.0 react@^18.2.0 react-dom@^18.2.0
不要な設定を削除し、プロジェクトを初期化します。
styles
CSSなどを管理するstylesディレクトリを作成します。globals.css
を移動します。
$ mkdir -p src/styles
$ mv src/app/globals.css src/styles/globals.css
globals.css
の内容を以下のように上書きします。
@tailwind base;
@tailwind components;
@tailwind utilities;
初期ページ
app/page.tsx
を上書きします。
import { type FC } from "react";
const Home: FC = () => {
return (
<div className="">
<div className="text-lg font-bold">Home</div>
<div>
<span className="text-blue-500">Hello</span>
<span className="text-red-500">World</span>
</div>
</div>
);
};
export default Home;
レイアウト
app/layout.tsx
を上書きします。
import "@/styles/globals.css";
import { type FC } from "react";
type RootLayoutProps = {
children: React.ReactNode;
};
export const metadata = {
title: "Sample",
description: "Generated by create next app",
};
const RootLayout: FC<RootLayoutProps> = (props) => {
return (
<html lang="ja">
<body className="">{props.children}</body>
</html>
);
};
export default RootLayout;
TailwindCSSの設定
TailwindCSSの設定を上書きします。
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
plugins: [],
}
export default config
TypeScriptの設定
TypeScriptの設定を上書きします。
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl" : ".",
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
スクリプトを追加
型チェックのスクリプトを追加します。
{
"name": "next-tsconfig-resolvejsonmodule",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
+ "typecheck": "tsc"
},
"dependencies": {
"next": "14.1.4"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.4",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
動作確認
ローカルで動作確認します。
$ pnpm run dev
コミットして作業結果を保存しておきます。
$ git add .
$ git commit -m "feat:新規にプロジェクトを作成し, 作業環境を構築"
実装
以下の通り実装することで、App Router の Server Component で URL を取得できます。
- middleware でリクエストヘッダーに
x-url
を追加 - Server Component でリクエストヘッダーの
x-url
から URL を取得
ここでは 3 つの環境で Server Component で URL を取得し画面に表示します。
- 普通のページ
- Dynamic Route
- URL クエリパラメータを含むページ
middlewareの作成
middleware.ts
を作成します。リクエストのヘッダーに x-url
を追加します。x-url
にはリクエストの URL が設定されます。
$ touch src/middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request: Request) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-url', request.url);
return NextResponse.next({
request: {
headers: requestHeaders
}
});
}
普通のページ
通常のページでリクエストヘッダーから x-url
を取得して表示します。
src/app/page.tsx
を上書きします。
import { FC } from "react";
import { headers } from "next/headers";
type Props = {};
const Page: FC<Props> = () => {
const requestUrl = headers().get("x-url");
return (
<>
<div className="p-5">
<div className="p-5 bg-blue-100">
<div className="p-5 bg-yellow-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">URL</span>
</div>
<div>
<span className="text-sm text-blue-800 py-2 px-3 bg-blue-100 rounded-md">
{requestUrl}
</span>
</div>
</div>
<div className="p-5 bg-red-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">Title</span>
</div>
<div>
<span className="text-base">Home</span>
</div>
</div>
</div>
</div>
</>
);
};
export default Page;
http://localhost:3000/ で動作確認します。
Dynamic Route, Dynamic Segment を作成
Dynamic Route を作成します。Dynamic Route とは、/product/[id]
のように、URL の一部が動的に変わるページのことです。Dynamic Segment とは、[id]
のように、[]
で囲まれた部分のことです。詳しくは公式ページを参照してください。
ここでは、src/app/product/\[id\]/page.tsx
を作成します。Dynamic Segment には、id
が設定されます。リクエストヘッダーから x-url
の値を取得して表示します。
$ mkdir -p src/app/product/\[id\]
$ touch src/app/product/\[id\]/page.tsx
import { FC } from "react";
import { headers } from "next/headers";
type Props = {
params: {
id: string;
};
};
const Page: FC<Props> = (props) => {
const requestUrl = headers().get("x-url");
return (
<>
<div className="p-5">
<div className="p-5 bg-blue-100">
<div className="p-5 bg-yellow-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">URL</span>
</div>
<div>
<span className="text-sm text-blue-800 py-2 px-3 bg-blue-100 rounded-md">
{requestUrl}
</span>
</div>
</div>
<div className="p-5 bg-red-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">Title</span>
</div>
<div>
<span className="text-base">Product</span>
</div>
</div>
<div className="p-5 bg-green-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">Route Segument</span>
</div>
<div>
<span className="text-sm text-blue-800 py-2 px-3 bg-blue-100 rounded-md">
id: {props.params.id}
</span>
</div>
</div>
</div>
</div>
</>
);
};
export default Page;
http://localhost:3000/product/1 で動作確認します。
URLクエリパラメータを含むページを作成
URL クエリパラメータを含むページを作成します。URL クエリパラメータとは、?
以降のパラメータのことです。例えば、?q=test
のように、q
がパラメータ名で、test
が値です。
ここでは、src/app/search/page.tsx
を作成します。URL クエリパラメータには、q
が設定されます。リクエストヘッダーから x-url
の値を取得して表示します。
$ mkdir -p src/app/search
$ touch src/app/search/page.tsx
import { FC } from "react";
import { headers } from "next/headers";
type Props = {
params: {
id: string;
};
// 全てのクエリパラメターの値を取得する
searchParams: { [key: string]: string | string[] | undefined };
// 特定のクエリパラメターの値のみを取得するよう型制御する。
// searchParams: { q: string };
};
const Page: FC<Props> = (props) => {
const requestUrl = headers().get("x-url");
return (
<>
<div className="p-5">
<div className="p-5 bg-blue-100">
<div className="p-5 bg-yellow-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">URL</span>
</div>
<div>
<span className="text-sm text-blue-800 py-2 px-3 bg-blue-100 rounded-md">
{requestUrl}
</span>
</div>
</div>
<div className="p-5 bg-red-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">Title</span>
</div>
<div>
<span className="text-base">Search</span>
</div>
</div>
<div className="p-5 bg-green-100 flex flex-col space-y-2">
<div>
<span className="text-lg font-bold">クエリパラメター</span>
</div>
<div>
<span className="text-sm text-blue-800 py-2 px-3 bg-blue-100 rounded-md">
q: {props.searchParams.q}
</span>
</div>
</div>
</div>
</div>
</>
);
};
export default Page;
http://localhost:3000/search?q=helllo で動作確認します。
コミットします。
$ git add .
$ git commit -m "feat:middlewareでリクエストヘッダーにx-urlを追加し、Server ComponentでURLを取得"
さいごに
App Router の Server Component で URL を取得する方法を紹介しました。
作業リポジトリは以下になります。
Discussion