---
name: web-navigation
description: Navigation and routing patterns for React web applications. Use when implementing React Router, Next.js routing, deep links, or handling navigation state.
---
# Web Navigation (React)
## React Router (v6)
### Basic Setup
```typescript
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
} />
} />
} />
} />
} />
);
}
```
### Nested Routes & Layouts
```typescript
// Layout with shared UI
function DashboardLayout() {
return (
{/* Child routes render here */}
);
}
// Routes
}>
} />
} />
} />
```
### Dynamic Routes
```typescript
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 (
User {id}
setSearchParams({ tab: t })}
/>
);
}
```
### Programmatic Navigation
```typescript
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
```typescript
import { Link, NavLink } from 'react-router-dom';
// Basic link
About
// With state
Checkout
// NavLink - active styling
isActive ? 'nav-link active' : 'nav-link'
}
>
Dashboard
```
---
## 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
```typescript
// app/layout.tsx - Root layout
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
// app/dashboard/layout.tsx - Nested layout
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
### Dynamic Routes
```typescript
// 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 (
User {id}
);
}
// Generate static params (optional)
export async function generateStaticParams() {
const users = await getUsers();
return users.map((user) => ({
id: user.id,
}));
}
```
### Programmatic Navigation
```typescript
'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
```typescript
import Link from 'next/link';
// Basic link
About
// With dynamic route
{user.name}
// With query params
Search
// Prefetching (default: true)
Dashboard
```
---
## Route Groups & Organization
### Auth-Protected vs Public Routes
```typescript
// React Router
{/* Public routes */}
} />
} />
{/* Protected 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
```typescript
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 (
}>
}>
{(resolvedUser) => }
);
}
```
### Next.js
```typescript
// app/users/[id]/loading.tsx
export default function Loading() {
return ;
}
// app/users/[id]/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
Something went wrong!
Try again
);
}
// app/users/[id]/not-found.tsx
export default function NotFound() {
return User not found
;
}
```
---
## Scroll Restoration
### React Router
```typescript
import { ScrollRestoration } from 'react-router-dom';
function App() {
return (
{/* ... */}
);
}
```
### Manual Scroll to Top
```typescript
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
```typescript
// 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) {
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 (
setFilters({ category: cat })}
/>
setFilters({ page: p })}
/>
);
}
```
---
## Navigation Guards
```typescript
// 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 (
<>
{blocker.state === 'blocked' && (
blocker.proceed()}
onCancel={() => blocker.reset()}
/>
)}
>
);
}
```
---
## Common Patterns
### Redirect After Action
```typescript
// 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
```typescript
function UserProfile() {
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab') || 'overview';
const tabs = ['overview', 'activity', 'settings'];
return (
{tabs.map((t) => (
setSearchParams({ tab: t })}
className={tab === t ? 'active' : ''}
>
{t}
))}
);
}
```
---
## 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 |