jpskill.com
🛠️ 開発・MCP コミュニティ

web-navigation

Navigation and routing patterns for React web applications. Use when implementing React Router, Next.js routing, deep links, or handling navigation state.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o web-navigation.zip https://jpskill.com/download/17867.zip && unzip -o web-navigation.zip && rm web-navigation.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17867.zip -OutFile "$d\web-navigation.zip"; Expand-Archive "$d\web-navigation.zip" -DestinationPath $d -Force; ri "$d\web-navigation.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して web-navigation.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → web-navigation フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] web-navigation

Web Navigation (React)

React Router (v6)

基本的な設定

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/users" element={<UsersPage />} />
        <Route path="/users/:id" element={<UserDetailPage />} />
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

ネストされたルートとレイアウト

// 共有 UI を持つレイアウト
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet /> {/* 子ルートはここにレンダリングされます */}
      </main>
    </div>
  );
}

// ルート
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="analytics" element={<Analytics />} />
    <Route path="settings" element={<Settings />} />
  </Route>
</Routes>

動的なルート

import { useParams, useSearchParams } from 'react-router-dom';

// Route: /users/:id
function UserDetailPage() {
  const { id } = useParams<{ id: string }>();
  const [searchParams, setSearchParams] = useSearchParams();

  const tab = searchParams.get('tab') || 'profile';

  return (
    <div>
      <h1>User {id}</h1>
      <TabBar
        active={tab}
        onChange={(t) => setSearchParams({ tab: t })}
      />
    </div>
  );
}

プログラムによるナビゲーション

import { useNavigate, useLocation } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();

  async function handleLogin() {
    await login(credentials);

    // 意図したページまたはデフォルトにリダイレクト
    const from = location.state?.from?.pathname || '/dashboard';
    navigate(from, { replace: true });
  }

  // その他のナビゲーションメソッド
  navigate('/users');           // 履歴に追加
  navigate('/users', { replace: true }); // 現在のエントリを置き換え
  navigate(-1);                 // 戻る
  navigate(1);                  // 進む
}

Link コンポーネント

import { Link, NavLink } from 'react-router-dom';

// 基本的なリンク
<Link to="/about">About</Link>

// state を使用
<Link to="/checkout" state={{ cartId: '123' }}>
  Checkout
</Link>

// NavLink - アクティブなスタイリング
<NavLink
  to="/dashboard"
  className={({ isActive }) =>
    isActive ? 'nav-link active' : 'nav-link'
  }
>
  Dashboard
</NavLink>

Next.js App Router

ファイルベースルーティング

app/
├── layout.tsx          # ルートレイアウト
├── page.tsx            # / ルート
├── about/
│   └── page.tsx        # /about ルート
├── users/
│   ├── page.tsx        # /users ルート
│   └── [id]/
│       └── page.tsx    # /users/:id ルート
├── (auth)/             # ルートグループ (URL セグメントなし)
│   ├── login/
│   │   └── page.tsx    # /login ルート
│   └── register/
│       └── page.tsx    # /register ルート
└── dashboard/
    ├── layout.tsx      # ダッシュボードレイアウト
    ├── page.tsx        # /dashboard
    └── settings/
        └── page.tsx    # /dashboard/settings

レイアウト

// app/layout.tsx - ルートレイアウト
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <Header />
          {children}
          <Footer />
        </Providers>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx - ネストされたレイアウト
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

動的なルート

// app/users/[id]/page.tsx
interface Props {
  params: { id: string };
  searchParams: { tab?: string };
}

export default function UserPage({ params, searchParams }: Props) {
  const { id } = params;
  const tab = searchParams.tab || 'profile';

  return (
    <div>
      <h1>User {id}</h1>
      <Tabs active={tab} />
    </div>
  );
}

// 静的な params を生成 (オプション)
export async function generateStaticParams() {
  const users = await getUsers();
  return users.map((user) => ({
    id: user.id,
  }));
}

プログラムによるナビゲーション

'use client';

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

function SearchForm() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  function handleSearch(query: string) {
    const params = new URLSearchParams(searchParams);
    params.set('q', query);
    router.push(`${pathname}?${params.toString()}`);
  }

  // ナビゲーションメソッド
  router.push('/dashboard');      // ナビゲート
  router.replace('/dashboard');   // 履歴なしで置き換え
  router.back();                  // 戻る
  router.forward();               // 進む
  router.refresh();               // サーバーコンポーネントをリフレッシュ
}

Link コンポーネント

import Link from 'next/link';

// 基本的なリンク
<Link href="/about">About</Link>

// 動的なルートを使用
<Link href={`/users/${user.id}`}>
  {user.name}
</Link>

// クエリパラメータを使用
<Link href={{ pathname: '/search', query: { q: 'react' } }}>
  Search
</Link>

// プリフェッチ (デフォルト: true)
<Link href="/dashboard" prefetch={false}>
  Dashboard
</Link>

ルートグループと構成

認証保護されたルート vs パブリックルート

// React Router
<Routes>
  {/* パブリックルート */}
  <Route path="/login" element={<LoginPage />} />
  <Route path="/register" element={<RegisterPage />} />

  {/* 保護されたルート */}
  <Route element={<RequireAuth />}>
    <Route path="/dashboard" element={<DashboardPage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>
</Routes>

// Next.js - ルートグループを使用
// app/(public)/login/page.tsx
// app/(protected)/dashboard
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Web Navigation (React)

React Router (v6)

Basic Setup

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/users" element={<UsersPage />} />
        <Route path="/users/:id" element={<UserDetailPage />} />
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

Nested Routes & Layouts

// Layout with shared UI
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

// Routes
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="analytics" element={<Analytics />} />
    <Route path="settings" element={<Settings />} />
  </Route>
</Routes>

Dynamic Routes

import { useParams, useSearchParams } from 'react-router-dom';

// Route: /users/:id
function UserDetailPage() {
  const { id } = useParams<{ id: string }>();
  const [searchParams, setSearchParams] = useSearchParams();

  const tab = searchParams.get('tab') || 'profile';

  return (
    <div>
      <h1>User {id}</h1>
      <TabBar
        active={tab}
        onChange={(t) => setSearchParams({ tab: t })}
      />
    </div>
  );
}

Programmatic Navigation

import { useNavigate, useLocation } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();

  async function handleLogin() {
    await login(credentials);

    // Redirect to intended page or default
    const from = location.state?.from?.pathname || '/dashboard';
    navigate(from, { replace: true });
  }

  // Other navigation methods
  navigate('/users');           // Push to history
  navigate('/users', { replace: true }); // Replace current entry
  navigate(-1);                 // Go back
  navigate(1);                  // Go forward
}

Link Component

import { Link, NavLink } from 'react-router-dom';

// Basic link
<Link to="/about">About</Link>

// With state
<Link to="/checkout" state={{ cartId: '123' }}>
  Checkout
</Link>

// NavLink - active styling
<NavLink
  to="/dashboard"
  className={({ isActive }) =>
    isActive ? 'nav-link active' : 'nav-link'
  }
>
  Dashboard
</NavLink>

Next.js App Router

File-Based Routing

app/
├── layout.tsx          # Root layout
├── page.tsx            # / route
├── about/
│   └── page.tsx        # /about route
├── users/
│   ├── page.tsx        # /users route
│   └── [id]/
│       └── page.tsx    # /users/:id route
├── (auth)/             # Route group (no URL segment)
│   ├── login/
│   │   └── page.tsx    # /login route
│   └── register/
│       └── page.tsx    # /register route
└── dashboard/
    ├── layout.tsx      # Dashboard layout
    ├── page.tsx        # /dashboard
    └── settings/
        └── page.tsx    # /dashboard/settings

Layouts

// app/layout.tsx - Root layout
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <Header />
          {children}
          <Footer />
        </Providers>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx - Nested layout
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

Dynamic Routes

// app/users/[id]/page.tsx
interface Props {
  params: { id: string };
  searchParams: { tab?: string };
}

export default function UserPage({ params, searchParams }: Props) {
  const { id } = params;
  const tab = searchParams.tab || 'profile';

  return (
    <div>
      <h1>User {id}</h1>
      <Tabs active={tab} />
    </div>
  );
}

// Generate static params (optional)
export async function generateStaticParams() {
  const users = await getUsers();
  return users.map((user) => ({
    id: user.id,
  }));
}

Programmatic Navigation

'use client';

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

function SearchForm() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  function handleSearch(query: string) {
    const params = new URLSearchParams(searchParams);
    params.set('q', query);
    router.push(`${pathname}?${params.toString()}`);
  }

  // Navigation methods
  router.push('/dashboard');      // Navigate
  router.replace('/dashboard');   // Replace without history
  router.back();                  // Go back
  router.forward();               // Go forward
  router.refresh();               // Refresh server components
}

Link Component

import Link from 'next/link';

// Basic link
<Link href="/about">About</Link>

// With dynamic route
<Link href={`/users/${user.id}`}>
  {user.name}
</Link>

// With query params
<Link href={{ pathname: '/search', query: { q: 'react' } }}>
  Search
</Link>

// Prefetching (default: true)
<Link href="/dashboard" prefetch={false}>
  Dashboard
</Link>

Route Groups & Organization

Auth-Protected vs Public Routes

// React Router
<Routes>
  {/* Public routes */}
  <Route path="/login" element={<LoginPage />} />
  <Route path="/register" element={<RegisterPage />} />

  {/* Protected routes */}
  <Route element={<RequireAuth />}>
    <Route path="/dashboard" element={<DashboardPage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>
</Routes>

// Next.js - use route groups
// app/(public)/login/page.tsx
// app/(protected)/dashboard/page.tsx
// app/(protected)/layout.tsx - add auth check

Loading & Error States

React Router

import { Suspense } from 'react';
import { Await, useLoaderData, defer } from 'react-router-dom';

// Loader
export async function loader({ params }) {
  return defer({
    user: getUser(params.id), // Promise
  });
}

// Component
function UserPage() {
  const { user } = useLoaderData();

  return (
    <Suspense fallback={<Spinner />}>
      <Await resolve={user} errorElement={<ErrorFallback />}>
        {(resolvedUser) => <UserProfile user={resolvedUser} />}
      </Await>
    </Suspense>
  );
}

Next.js

// app/users/[id]/loading.tsx
export default function Loading() {
  return <Spinner />;
}

// app/users/[id]/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// app/users/[id]/not-found.tsx
export default function NotFound() {
  return <div>User not found</div>;
}

Scroll Restoration

React Router

import { ScrollRestoration } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>{/* ... */}</Routes>
      <ScrollRestoration />
    </BrowserRouter>
  );
}

Manual Scroll to Top

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToTop() {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}

Deep Linking / Query Params

// Custom hook for type-safe query params
import { useSearchParams } from 'react-router-dom';

interface Filters {
  category?: string;
  sort?: 'asc' | 'desc';
  page?: number;
}

function useFilters() {
  const [searchParams, setSearchParams] = useSearchParams();

  const filters: Filters = {
    category: searchParams.get('category') || undefined,
    sort: (searchParams.get('sort') as 'asc' | 'desc') || undefined,
    page: Number(searchParams.get('page')) || 1,
  };

  function setFilters(newFilters: Partial<Filters>) {
    const params = new URLSearchParams(searchParams);

    Object.entries(newFilters).forEach(([key, value]) => {
      if (value !== undefined && value !== null) {
        params.set(key, String(value));
      } else {
        params.delete(key);
      }
    });

    setSearchParams(params);
  }

  return { filters, setFilters };
}

// Usage
function ProductList() {
  const { filters, setFilters } = useFilters();

  return (
    <div>
      <CategorySelect
        value={filters.category}
        onChange={(cat) => setFilters({ category: cat })}
      />
      <ProductGrid products={products} />
      <Pagination
        page={filters.page}
        onChange={(p) => setFilters({ page: p })}
      />
    </div>
  );
}

Navigation Guards

// Prevent navigation with unsaved changes
import { useBlocker } from 'react-router-dom';

function EditForm() {
  const [isDirty, setIsDirty] = useState(false);

  const blocker = useBlocker(
    ({ currentLocation, nextLocation }) =>
      isDirty && currentLocation.pathname !== nextLocation.pathname
  );

  return (
    <>
      <form onChange={() => setIsDirty(true)}>
        {/* form fields */}
      </form>

      {blocker.state === 'blocked' && (
        <ConfirmDialog
          message="You have unsaved changes. Leave anyway?"
          onConfirm={() => blocker.proceed()}
          onCancel={() => blocker.reset()}
        />
      )}
    </>
  );
}

Common Patterns

Redirect After Action

// After form submission
async function handleSubmit(data: FormData) {
  const result = await createItem(data);
  navigate(`/items/${result.id}`);
}

// After login
async function handleLogin() {
  await login(credentials);
  const redirectTo = searchParams.get('redirect') || '/dashboard';
  navigate(redirectTo, { replace: true });
}

Tab Navigation with URL

function UserProfile() {
  const [searchParams, setSearchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'overview';

  const tabs = ['overview', 'activity', 'settings'];

  return (
    <div>
      <nav>
        {tabs.map((t) => (
          <button
            key={t}
            onClick={() => setSearchParams({ tab: t })}
            className={tab === t ? 'active' : ''}
          >
            {t}
          </button>
        ))}
      </nav>
      <TabContent tab={tab} />
    </div>
  );
}

Common Issues

Issue Solution
Route not matching Check route order (specific before dynamic)
Back button doesn't work Use navigate() not window.location
State lost on refresh Store in URL params, not just state
Scroll position wrong Add ScrollRestoration component
404 in production (SPA) Configure server for SPA fallback