レビュー済み·難易度: 初級·更新: 2026-04-18

Next.js で現在の検索条件を保持したままページ遷移するリンクを生成する

一覧ページの検索語・フィルタ・sort・page を URL クエリとして保持し、詳細ページや別導線へ遷移後に戻ったとき条件が復元されるリンク生成パターン。

nextjsroutingsearch-filter

対応バージョン

nextjs 15react 19

前提環境

Next.js App Router の Link コンポーネントと URL クエリパラメータの基本を理解していること

概要

一覧ページで検索語・フィルタ・sort・page を URL クエリとして保持した状態で詳細ページへ遷移し、詳細ページの「一覧へ戻る」リンクで同じ条件に戻れるパターンを示す。searchParams をそのまま文字列化して back クエリに渡す方法と、詳細ページ側でデコードして戻りリンクを生成する方法を実装する。

インストール

# 追加インストールは不要

実装

クエリを保持したままカードリンクを生成する

// components/SampleCardLink.tsx
import Link from "next/link";

type Props = {
  slug: string;
  /** 一覧ページの現在のクエリ文字列(searchParams を URLSearchParams で文字列化したもの) */
  listQuery?: string;
  children: React.ReactNode;
};

export function SampleCardLink({ slug, listQuery, children }: Props) {
  const backParam = listQuery ? `?back=${encodeURIComponent(listQuery)}` : "";
  return (
    <Link href={`/samples/${slug}${backParam}`}>
      {children}
    </Link>
  );
}

一覧ページで listQuery を組み立てて渡す

// app/samples/page.tsx(抜粋)
import { SampleCardLink } from "@/components/SampleCardLink";

export default async function SamplesPage({ searchParams }: Props) {
  const params = await searchParams;

  // 現在のクエリ文字列をそのまま文字列化
  const listQuery = new URLSearchParams(
    Object.entries(params).filter(
      (entry): entry is [string, string] => typeof entry[1] === "string"
    )
  ).toString();

  return (
    <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
      {filtered.map((sample) => (
        <SampleCardLink key={sample.slug} slug={sample.slug} listQuery={listQuery}>
          <SampleCard sample={sample} query={filter.q} />
        </SampleCardLink>
      ))}
    </div>
  );
}

詳細ページで「一覧へ戻る」リンクを生成する

// app/samples/[slug]/page.tsx(抜粋)
import Link from "next/link";

type Props = {
  params: Promise<{ slug: string }>;
  searchParams: Promise<Record<string, string | string[] | undefined>>;
};

export default async function SampleDetailPage({ params, searchParams }: Props) {
  const { slug } = await params;
  const sp = await searchParams;

  const back = typeof sp.back === "string" ? sp.back : "";
  const backHref = back ? `/samples?${back}` : "/samples";

  return (
    <div>
      <Link
        href={backHref}
        className="mb-4 inline-flex items-center gap-1 text-sm text-blue-600 hover:underline"
      >
        ← サンプル一覧へ戻る
      </Link>
      {/* ...詳細コンテンツ */}
    </div>
  );
}

back クエリを検証してから使う

// lib/navigation.ts

/**
 * back クエリを検証して安全な戻り先 URL を返す。
 * 外部 URL や不正な文字列を拒否して /samples にフォールバックする。
 */
export function safeBackHref(back: string | undefined): string {
  if (!back) return "/samples";

  try {
    // URLSearchParams として解釈できるかチェック
    const decoded = decodeURIComponent(back);
    new URLSearchParams(decoded); // 不正な場合は例外
    return `/samples?${decoded}`;
  } catch {
    return "/samples";
  }
}
// app/samples/[slug]/page.tsx(安全版)
import { safeBackHref } from "@/lib/navigation";

const backHref = safeBackHref(typeof sp.back === "string" ? sp.back : undefined);

ポイント

  • back パラメータには一覧ページのクエリ文字列をそのまま encodeURIComponent して渡す。デコード後に /samples?${back} として使えるシンプルな設計
  • back クエリの値は必ず検証してから使う。外部サイトへの open redirect を防ぐため、/samples? プレフィックスを付けて内部 URL にのみ使用する
  • new URLSearchParams(decoded) で解析できるかチェックすることで、不正な文字列を安全に弾ける
  • back がない場合(直接アクセスや別経路)は /samples にフォールバックする。back を必須にすると直アクセス時にリンクが消える
  • page を含むクエリを保持するので、2 ページ目を見ていたユーザーが詳細を見て戻ったとき、同じページ位置に戻ることができる

注意点

nextjs-url-filter-reset は条件のリセット操作。tailwind-filter-chip-ui はチップ表示。これは現在のすべてのクエリパラメータを保持したまま別ページへ遷移するリンク生成に特化。詳細から一覧に戻る「戻るリンク」パターンも含む。

関連サンプル