On This Page
When building Next.js apps, most developers write route paths directly in their code (means they hard-code the route path). This seems easy and fast at first, but it creates big problems when your app grows bigger.
The Problem with Hardcoded Routes
Consider this common pattern you've probably seen (or written) before:
// ❌ Problematic approachimport Link from 'next/link'import { useRouter } from 'next/router'export default function Navbar() {const router = useRouter()const handleNavigation = () => {router.push('/dashboard/user/profile')}return (<nav><Link href="/about">About</Link><Link href="/contact">Contact</Link><Link href="/blog">Blog</Link><Link href="/dashboard/analytics">Analytics</Link><button onClick={handleNavigation}>Profile</button></nav>)}
In above code snippet all the route path are hard-coded.
Why This Approach Fails
1. Maintenance Issue
When you need to change a route from /blog
to /articles
, you'll have to hunt through your entire codebase to find and update every occurrence.
2. Chances of Typos
It's easy to mistype /dashbord
instead of /dashboard
, leading to broken links that might not be caught until production.
3. No IntelliSense Support Your IDE can't help you with autocomplete or catch errors when routes are just strings scattered throughout your code.
4. Team Collaboration Issues Different team members might use slightly different paths for the same route, creating inconsistencies.
5. Testing Difficulties Hardcoded routes make it harder to write comprehensive tests and mock navigation behavior.
The Solution: Centralized Route Management
The solution is to create a centralized route configuration that serves as the single source of truth for all your application's routes.
Let me show you how with step by step process.
Step 1: Create Your Routes Configuration
First, create a dedicated file for your routes. I recommend placing it in a constants
or config
directory:
/src/app/components/constants└── routes.ts/lib
Step 2: Define Your Routes Object
// constants/routes.tsexport const ROUTES = {// Public routesHOME: '/',ABOUT: '/about',CONTACT: '/contact',PRICING: '/pricing',// Auth routesLOGIN: '/auth/login',REGISTER: '/auth/register',FORGOT_PASSWORD: '/auth/forgot-password',// Blog routesBLOG: '/blog',BLOG_DETAIL: (slug: string) => `/blog/${slug}`,BLOG_CATEGORY: (category: string) => `/blog/category/${category}`,// Dashboard routesDASHBOARD: '/dashboard',DASHBOARD_ANALYTICS: '/dashboard/analytics',DASHBOARD_SETTINGS: '/dashboard/settings',DASHBOARD_PROFILE: '/dashboard/profile',// Dynamic user routesUSER_PROFILE: (userId: string) => `/user/${userId}`,USER_POSTS: (userId: string) => `/user/${userId}/posts`,// API routes (useful for fetch calls)API: {USERS: '/api/users',POSTS: '/api/posts',AUTH: '/api/auth',},} as const// Export route groups for better organizationexport const AUTH_ROUTES = {LOGIN: ROUTES.LOGIN,REGISTER: ROUTES.REGISTER,FORGOT_PASSWORD: ROUTES.FORGOT_PASSWORD,} as constexport const DASHBOARD_ROUTES = {HOME: ROUTES.DASHBOARD,ANALYTICS: ROUTES.DASHBOARD_ANALYTICS,SETTINGS: ROUTES.DASHBOARD_SETTINGS,PROFILE: ROUTES.DASHBOARD_PROFILE,} as const
Step 3: Use Routes in Your Components
Now your components become much cleaner and more maintainable:
// components/Navbar.tsximport Link from 'next/link'import { useRouter } from 'next/router'import { ROUTES, DASHBOARD_ROUTES } from '@/constants/routes'export default function Navbar() {const router = useRouter()const handleProfileNavigation = () => {router.push(DASHBOARD_ROUTES.PROFILE)}return (<nav className="flex space-x-4"><Link href={ROUTES.ABOUT} className="hover:text-blue-500">About</Link><Link href={ROUTES.CONTACT} className="hover:text-blue-500">Contact</Link><Link href={ROUTES.BLOG} className="hover:text-blue-500">Blog</Link><Link href={DASHBOARD_ROUTES.ANALYTICS} className="hover:text-blue-500">Analytics</Link><buttononClick={handleProfileNavigation}className="bg-blue-500 text-white px-4 py-2 rounded">Profile</button></nav>)}
Step 4: Handle Dynamic Routes Elegantly
Dynamic routes become much more readable and type-safe:
// components/BlogList.tsximport Link from 'next/link'import { ROUTES } from '@/constants/routes'interface Post {slug: stringtitle: stringcategory: string}interface BlogListProps {posts: Post[]}export default function BlogList({ posts }: BlogListProps) {return (<div className="space-y-4">{posts.map((post) => (<article key={post.slug} className="border p-4 rounded"><h2 className="text-xl font-bold"><Link href={ROUTES.BLOG_DETAIL(post.slug)} className="hover:text-blue-500">{post.title}</Link></h2><Linkhref={ROUTES.BLOG_CATEGORY(post.category)}className="text-sm text-gray-500 hover:text-blue-500">Category: {post.category}</Link></article>))}</div>)}
Benefits of This Approach
-
No Repetition – Define once, use everywhere.
-
No Typos – Reduce the risk of route misnaming.
-
Easy Refactor – Change the route in one place, and it updates across the app.
-
Scalable – As your app grows, this structure keeps things organized.
-
Improved Development Experience – You get autocomplete and type safety in editors like VSCode.
Centralizing your routes in a routes.ts
file is a small habit that pays off big in the long run. It keeps your code DRY, readable, and easier to maintain, especially in teams and large-scale projects.
Start doing this early in your Next.js projects, and you’ll thank yourself later.