概要
defineTypedHref / defineTypedHrefWithNuqs に metadataBase option と $url() helper を追加し、絶対 URL を型安全に生成できるようにする。
背景
現在の $href() は相対パス (/users/42) のみを返す。これは:
- ✅
<Link href> / router.push() / metadata.openGraph.url (Next.js が metadataBase で自動解決) — 問題なし
- ❌ Next.js の URL 解決機構を経由しない出力先 — 手動で
process.env.NEXT_PUBLIC_SITE_URL + $href(...) を書く必要があり、型安全性が崩れる
利用用途 (page.tsx 由来の URL に限定)
| 用途 |
例 |
| メール内リンク |
パスワードリセット、招待、通知 |
| Push / Slack / Discord 通知 |
bot からの遷移リンク |
app/sitemap.ts |
url フィールドは絶対 URL 仕様 |
| RSS / Atom フィード |
<link> / <guid> |
| JSON-LD 構造化データ |
@id / url |
| クリップボードコピー / 共有 |
"リンクをコピー" ボタン |
| QR コード生成 |
エンコードする URL |
動的 og:image |
Server Component から組み立てる場合 |
対象外 (Next.js typegen の AppRoutes に含まれない)
- API route handler (
route.ts) — Stripe webhook 等
- OAuth callback URL (route handler の場合)
- Server Action endpoint
これらは process.env.NEXT_PUBLIC_SITE_URL + "/api/..." 等で手動構築する。
提案 API
設定
// lib/href.ts
import { defineTypedHref } from "@plainbrew/next-typed-href";
import type { AppRoutes, ParamsOf } from "@/../.next/types/routes";
type AppRouteParamsMap = { [Route in AppRoutes]: ParamsOf<Route> };
export const { $href, $url } = defineTypedHref
.routes<AppRoutes, AppRouteParamsMap>()
.withOptions({ metadataBase: new URL("https://acme.com") });
使用
// 相対パス (Next.js Metadata / <Link> / router で使用)
$href({ route: "/users/[id]", routeParams: { id: "42" } });
// => "/users/42"
// 絶対 URL (メール / 通知 / sitemap 等)
$url({ route: "/users/[id]", routeParams: { id: "42" } });
// => "https://acme.com/users/42"
型レベルの工夫
metadataBase 未設定なら $url を生やさない (呼ぶと型エラー):
.withOptions({}) // → { $href } のみ
.withOptions({ metadataBase: new URL(...) }); // → { $href, $url }
Next.js Metadata との統合例
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!),
};
// lib/href.ts
export const { $href, $url } = defineTypedHref
.routes<AppRoutes, AppRouteParamsMap>()
.withOptions({ metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!) });
// app/posts/[slug]/page.tsx
export async function generateMetadata({ params }) {
const { slug } = await params;
return {
alternates: { canonical: $href({ route: "/posts/[slug]", routeParams: { slug } }) },
openGraph: { url: $url({ route: "/posts/[slug]", routeParams: { slug } }) },
};
}
// app/sitemap.ts
export default function sitemap(): MetadataRoute.Sitemap {
return posts.map((p) => ({
url: $url({ route: "/posts/[slug]", routeParams: { slug: p.slug } }),
lastModified: p.updatedAt,
}));
}
実装スケッチ
$url は本質的に 1 行:
function $url(opts): string {
return new URL($href(opts), metadataBase).toString();
}
URL constructor が trailing slash 正規化と相対パス解決をすべて処理する (Next.js の metadataBase 解決ロジックを再実装する必要なし)。
nuqs 側
defineTypedHrefWithNuqs 側も同様に $url を生やす:
const { $href, $url } = defineTypedHrefWithNuqs
.routes<AppRoutes, AppRouteParamsMap>()
.withOptions({ metadataBase: new URL("https://acme.com") })
.nuqs({ "/search": { q: parseAsString } });
$url({ route: "/search", searchParams: { q: "hello" } });
// => "https://acme.com/search?q=hello"
テスト計画
関連
概要
defineTypedHref/defineTypedHrefWithNuqsにmetadataBaseoption と$url()helper を追加し、絶対 URL を型安全に生成できるようにする。背景
現在の
$href()は相対パス (/users/42) のみを返す。これは:<Link href>/router.push()/metadata.openGraph.url(Next.js がmetadataBaseで自動解決) — 問題なしprocess.env.NEXT_PUBLIC_SITE_URL + $href(...)を書く必要があり、型安全性が崩れる利用用途 (
page.tsx由来の URL に限定)app/sitemap.tsurlフィールドは絶対 URL 仕様<link>/<guid>@id/urlog:image対象外 (Next.js typegen の
AppRoutesに含まれない)route.ts) — Stripe webhook 等これらは
process.env.NEXT_PUBLIC_SITE_URL + "/api/..."等で手動構築する。提案 API
設定
使用
型レベルの工夫
metadataBase未設定なら$urlを生やさない (呼ぶと型エラー):Next.js Metadata との統合例
実装スケッチ
$urlは本質的に 1 行:URLconstructor が trailing slash 正規化と相対パス解決をすべて処理する (Next.js のmetadataBase解決ロジックを再実装する必要なし)。nuqs側defineTypedHrefWithNuqs側も同様に$urlを生やす:テスト計画
metadataBase設定時に$urlが型に存在し、未設定時に存在しない$urlが$href + metadataBaseの組み合わせを返す[id]/[...slug]) を含む route で正しく解決されるmetadataBaseがサブパス付き (例:https://acme.com/app) でも正しく解決される$urlでsearchParamsが含まれるpage.tsxのみ・API route は対象外)」セクションを追加関連
metadataBase: https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase.routes()の explicit type arguments 必須化)