Next.js Routing & Navigation
Requirements
Route Props Helpers (Next.js 15.5+)
- Use globally available
PageProps,LayoutProps,RouteContexttypes - no imports needed - RouteContext: Use for typing route handler context parameters with proper type inference
- PageProps: Use for page components with typed params and searchParams
- LayoutProps: Use for layout components with children and parallel route slots
- Automatically includes typed params, searchParams, children, and parallel route slots
- Always await Promise-based params and searchParams
Typed Routes (Next.js 15.5+)
- TypeScript will catch invalid routes in
<Link>components at compile time - Automatically generates types based on your file structure
- Use
Routetype for props - Use
as Routefor non-literal strings (e.g.,('/blog' + slug) as Route)
Custom Param Hooks
- NEVER use useParams from Next.js directly - use typed alternatives from
@/hooks/use-params - Use unique param names (e.g.,
[userId]not[id]) for better type inference - useTParams(): For specific app routes with exact typing
- useLayoutParams(): For layout components, returns all params as optional
- useLayoutParams(): For layout components with route prefix, exact route params required + prefix params optional
URL Query State
- NEVER use useSearchParams from Next.js - always use nuqs for URL query state
- Use appropriate parsers based on parameter data type
- Configure history mode and clearOnDefault
- Handle URL updates with void to prevent promise-related linting issues
Examples
return ( Product: {id} Category: {category} ); }
// Multiple parameters export async function DELETE( request: Request, ctx: RouteContext<'/api/users/[id]/posts/[postId]'> ) { const { id, postId } = await ctx.params; return Response.json({ userId: id, postId }); }
// Optional parameters export async function PUT( request: Request, ctx: RouteContext<'/api/categories/[[...slug]]'> ) { const { slug } = await ctx.params; // slug: string[] | undefined return Response.json({ segments: slug }); }
// Usage const [filter, setFilter] = useFilterState(); void setFilter('active');
// Usage in components import Link from 'next/link';
// ✅ Type-safe links
// ✅ Non-literal strings with Route type const slug = 'nextjs';
// ❌ TypeScript will catch invalid routes at compile time
// ✅ For specific routes with exact typing const PatientPage = () => { const params = useTParams<'/patients/[patientId]'>(); params.patientId; // string - guaranteed to exist };
// ✅ For layouts - all params optional const RootLayout = ({ children }) => { const params = useLayoutParams(); params.patientId; // string | undefined params.complaintId; // string | undefined };
// ✅ For layouts with route prefix - exact + optional const ComplaintLayout = ({ children }) => { const params = useLayoutParams<'/complaints/[complaintId]'>(); params.complaintId; // string - required for exact match params.someOtherParam; // string | undefined - from related routes };
// ❌ Don't use manual typing for page props interface Props { params: Promise<{ slug: string }>; children: React.ReactNode; }
// ❌ Don't use raw useParams - use typed alternatives import { useParams } from 'next/navigation'; const params = useParams();
// ❌ Don't use useSearchParams import { useSearchParams } from 'next/navigation'; const searchParams = useSearchParams();
// ❌ Don't use string concatenation for routes
router.push(/patients/${id}); // Use typed routes instead