best practice to manage routes in nextjs

Stop Hardcoding Routes in Next.js: Use This Scalable Pattern Instead

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 approach
import 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.ts
export const ROUTES = {
// Public routes
HOME: '/',
ABOUT: '/about',
CONTACT: '/contact',
PRICING: '/pricing',
// Auth routes
LOGIN: '/auth/login',
REGISTER: '/auth/register',
FORGOT_PASSWORD: '/auth/forgot-password',
// Blog routes
BLOG: '/blog',
BLOG_DETAIL: (slug: string) => `/blog/${slug}`,
BLOG_CATEGORY: (category: string) => `/blog/category/${category}`,
// Dashboard routes
DASHBOARD: '/dashboard',
DASHBOARD_ANALYTICS: '/dashboard/analytics',
DASHBOARD_SETTINGS: '/dashboard/settings',
DASHBOARD_PROFILE: '/dashboard/profile',
// Dynamic user routes
USER_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 organization
export const AUTH_ROUTES = {
LOGIN: ROUTES.LOGIN,
REGISTER: ROUTES.REGISTER,
FORGOT_PASSWORD: ROUTES.FORGOT_PASSWORD,
} as const
export 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.tsx
import 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>
<button
onClick={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.tsx
import Link from 'next/link'
import { ROUTES } from '@/constants/routes'
interface Post {
slug: string
title: string
category: 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>
<Link
href={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.

Read Next

Codevertiser Magazine

Subscribe for a regular dose of coding challenges and insightful articles, delivered straight to your inbox