Modern Routing Navigation Implementation

1. React Router v6 Nested Routes

Feature API Description Benefit
BrowserRouter <BrowserRouter> HTML5 history API for clean URLs without hash (#) SEO-friendly, native browser navigation, back/forward support
Routes & Route <Routes><Route path element /> Declarative route configuration with element-based rendering Type-safe, composable, no render props needed
Nested Routes NEW <Outlet /> Parent routes render child routes using Outlet component Layout composition, shared UI, hierarchical routing
useNavigate navigate('/path') Programmatic navigation hook replacing useHistory Simpler API, TypeScript support, relative navigation
useParams & useSearchParams useParams(), useSearchParams() Access URL parameters and query strings in components Type-safe parameter extraction, URLSearchParams API
Index Routes <Route index element /> Default child route when parent path matches exactly Dashboard defaults, tab navigation, nested layouts

Example: React Router v6 Nested Routes

import { BrowserRouter, Routes, Route, Outlet, Link, useParams } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          
          {/* Nested routes */}
          <Route path="dashboard" element={<Dashboard />}>
            <Route index element={<DashboardHome />} />
            <Route path="profile" element={<Profile />} />
            <Route path="settings" element={<Settings />} />
          </Route>
          
          {/* Dynamic segments */}
          <Route path="users/:userId" element={<UserDetail />} />
          
          {/* Catch-all */}
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

// Layout with Outlet
function Layout() {
  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/dashboard">Dashboard</Link>
      </nav>
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

// Dashboard with nested routes
function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <Link to="/dashboard">Overview</Link>
        <Link to="/dashboard/profile">Profile</Link>
        <Link to="/dashboard/settings">Settings</Link>
      </nav>
      <Outlet /> {/* Nested child routes */}
    </div>
  );
}

// Access params
function UserDetail() {
  const { userId } = useParams();
  const [searchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'overview';
  
  return <div>User: {userId}, Tab: {tab}</div>;
}

// Programmatic navigation
function LoginForm() {
  const navigate = useNavigate();
  
  const handleSubmit = async (credentials) => {
    await login(credentials);
    navigate('/dashboard', { replace: true });
  };
}
v6 Changes: Removed <Switch> (use <Routes>), removed render/component props (use element), removed useHistory (use useNavigate), relative paths by default

2. Next.js App Router File-based

Concept File Convention Description Feature
App Router NEW app/ directory File-system based routing with React Server Components support Server components, streaming, layouts, loading states
page.js app/blog/page.js Defines route segment UI, makes path publicly accessible Route endpoints, unique per segment
layout.js app/layout.js Shared UI that wraps child layouts and pages, preserves state Persistent navigation, shared headers, nested layouts
loading.js app/blog/loading.js Loading UI with Suspense, instant loading state Automatic loading boundaries, streaming support
error.js app/blog/error.js Error boundary UI, automatically wraps route segment Granular error handling, error recovery
Dynamic Routes [slug]/page.js Dynamic segments using square brackets, catch-all routes Blog posts, product pages, user profiles
Route Groups (group)/page.js Organize routes without affecting URL structure Team organization, different layouts, logical grouping

Example: Next.js App Router File Structure

app/
├── layout.js           # Root layout (required)
├── page.js             # Home page (/)
├── loading.js          # Global loading UI
├── error.js            # Global error boundary
├── not-found.js        # 404 page
├── (marketing)/        # Route group (doesn't affect URL)
│   ├── about/
│   │   └── page.js     # /about
│   └── contact/
│       └── page.js     # /contact
├── dashboard/
│   ├── layout.js       # Dashboard layout
│   ├── page.js         # /dashboard
│   ├── loading.js      # Dashboard loading
│   ├── error.js        # Dashboard errors
│   └── [teamId]/
│       ├── page.js     # /dashboard/[teamId]
│       └── settings/
│           └── page.js # /dashboard/[teamId]/settings
└── blog/
    ├── [...slug]/      # Catch-all: /blog/a, /blog/a/b, etc.
    │   └── page.js
    └── [[...slug]]/    # Optional catch-all: includes /blog
        └── page.js

// app/layout.js - Root Layout (Required)
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <nav>Global Navigation</nav>
        {children}
        <footer>Global Footer</footer>
      </body>
    </html>
  );
}

// app/dashboard/layout.js - Nested Layout
export default function DashboardLayout({ children }) {
  return (
    <div>
      <aside>Dashboard Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

// app/dashboard/[teamId]/page.js - Dynamic Route
export default function TeamPage({ params }) {
  return <h1>Team: {params.teamId}</h1>;
}

// Generate static params at build time
export async function generateStaticParams() {
  const teams = await fetchTeams();
  return teams.map(team => ({ teamId: team.id }));
}

// app/dashboard/loading.js - Loading UI
export default function Loading() {
  return <div>Loading dashboard...</div>;
}

// app/dashboard/error.js - Error Boundary
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Special Files

File Purpose
layout.js Shared UI for segment
page.js Route endpoint UI
loading.js Loading UI with Suspense
error.js Error boundary
template.js Re-render on navigation
not-found.js 404 UI
route.js API endpoint
// Link component
import Link from 'next/link';

<Link href="/dashboard">Dashboard</Link>
<Link href={`/blog/${slug}`}>Post</Link>

// Programmatic navigation
'use client';
import { useRouter } from 'next/navigation';

function Button() {
  const router = useRouter();
  
  return (
    <button onClick={() => router.push('/dashboard')}>
      Go to Dashboard
    </button>
  );
}

// Prefetch on hover (default)
<Link href="/slow-page" prefetch={true}>
  Prefetched Link
</Link>

App Router Benefits

  • Server Components: Zero-bundle components with direct DB access
  • Streaming: Progressive rendering with Suspense boundaries
  • Layouts: Persistent UI that doesn't re-render on navigation
  • File Conventions: loading.js, error.js for automatic boundaries

3. Vue Router Composition API

Feature API Description Use Case
createRouter createRouter({ routes }) Create router instance with history mode and route configuration SPA routing, history management, route guards
useRouter & useRoute useRouter(), useRoute() Composition API hooks for router instance and current route Programmatic navigation, route information access
Nested Routes children: [] Child routes with nested RouterView components Complex layouts, multi-level navigation
Named Views <RouterView name="sidebar" /> Multiple RouterView components in same route Multi-panel layouts, sidebar + main content
Route Meta meta: { auth: true } Attach custom metadata to routes for guards, breadcrumbs Authentication, permissions, analytics tracking
Lazy Loading component: () => import() Code-split route components with dynamic imports Optimize initial bundle, load routes on-demand

Example: Vue Router with Composition API

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/dashboard',
    component: () => import('../layouts/Dashboard.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'DashboardHome',
        component: () => import('../views/DashboardHome.vue')
      },
      {
        path: 'profile',
        name: 'Profile',
        component: () => import('../views/Profile.vue')
      },
      {
        path: 'settings',
        name: 'Settings',
        component: () => import('../views/Settings.vue'),
        meta: { title: 'Settings' }
      }
    ]
  },
  {
    path: '/user/:id',
    name: 'UserDetail',
    component: () => import('../views/UserDetail.vue'),
    props: true // Pass route params as props
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition;
    return { top: 0 };
  }
});

export default router;

// Component using Composition API
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { computed } from 'vue';

const router = useRouter();
const route = useRoute();

// Access route params
const userId = computed(() => route.params.id);
const query = computed(() => route.query);

// Programmatic navigation
const navigateToDashboard = () => {
  router.push({ name: 'Dashboard' });
};

const goBack = () => {
  router.back();
};

// Navigation with params
const viewUser = (id) => {
  router.push({ 
    name: 'UserDetail', 
    params: { id },
    query: { tab: 'profile' }
  });
};
</script>

<template>
  <div>
    <RouterLink to="/dashboard">Dashboard</RouterLink>
    <RouterLink :to="{ name: 'Profile' }">Profile</RouterLink>
    <RouterView />
  </div>
</template>
Method Description
push() Navigate, add history entry
replace() Navigate, replace current entry
go(n) Move n steps in history
back() Go back one step
forward() Go forward one step

Route Object Properties

Property Description
route.path Current path string
route.params Dynamic route parameters
route.query Query parameters object
route.hash URL hash
route.name Route name
route.meta Route metadata

4. Code Splitting Route-based Lazy Loading

Strategy Implementation Description Benefit
Route-based Splitting React.lazy(() => import()) Split bundles at route boundaries, load on navigation Smaller initial bundle, faster TTI, pay-as-you-go loading
Suspense Boundary <Suspense fallback> Show loading state while lazy component loads Better UX, graceful loading, error boundaries
Prefetching <link rel="prefetch"> Load route bundle during browser idle time Instant navigation, zero loading delay
Preloading <link rel="preload"> High-priority loading for critical next routes Faster subsequent navigation, predictive loading
Magic Comments /* webpackChunkName */ Name chunks for better debugging and caching Named bundles, cache optimization, debugging

Example: Route-based Code Splitting

// React Router with lazy loading
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import(
  /* webpackChunkName: "dashboard" */
  /* webpackPrefetch: true */
  './pages/Dashboard'
));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

// Loading component
function LoadingSpinner() {
  return <div className="spinner">Loading...</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Next.js automatic code splitting
// pages/dashboard.js - automatically code-split
export default function Dashboard() {
  return <div>Dashboard</div>;
}

// Vue Router lazy loading
const routes = [
  {
    path: '/dashboard',
    component: () => import(
      /* webpackChunkName: "dashboard" */
      './views/Dashboard.vue'
    )
  }
];

// Prefetch on hover
import { useEffect } from 'react';

function NavLink({ to, children }) {
  const prefetchRoute = () => {
    // Prefetch route component
    import(/* webpackPrefetch: true */ `./pages/${to}`);
  };
  
  return (
    <a 
      href={to} 
      onMouseEnter={prefetchRoute}
      onFocus={prefetchRoute}
    >
      {children}
    </a>
  );
}

Bundle Strategies

Strategy When to Use
Route-based Main navigation routes
Component-based Heavy modals, charts, editors
Vendor splitting Separate node_modules
Common chunks Shared dependencies

Performance Metrics

  • Initial Bundle: Reduce 40-60% with route splitting
  • TTI: Time to Interactive improves by 30-50%
  • FCP: First Contentful Paint faster with smaller bundles
  • Cache Hit: Better caching with stable chunk names

5. Dynamic Import Module Loading

Technique Syntax Description Use Case
Dynamic Import import('module') Promise-based module loading at runtime, creates separate chunk Conditional loading, on-demand features, reduce initial bundle
Conditional Loading if (condition) import() Load modules based on runtime conditions (feature flags, user role) A/B testing, premium features, device-specific code
Import on Interaction onClick={() => import()} Lazy load when user interacts with UI element Modals, tooltips, rich editors, heavy components
Import on Visibility IntersectionObserver Load module when element enters viewport Below-fold content, infinite scroll, image galleries
Named Exports import().then(m => m.fn) Access specific exports from dynamically imported module Tree-shaking with dynamic imports, selective loading

Example: Dynamic Import Patterns

// Basic dynamic import
button.addEventListener('click', async () => {
  const module = await import('./heavyModule.js');
  module.initialize();
});

// React component lazy loading
function Editor() {
  const [EditorComponent, setEditorComponent] = useState(null);
  
  const loadEditor = async () => {
    const { default: Editor } = await import('react-quill');
    setEditorComponent(() => Editor);
  };
  
  useEffect(() => {
    loadEditor();
  }, []);
  
  if (!EditorComponent) return <div>Loading editor...</div>;
  return <EditorComponent />;
}

// Conditional loading based on feature flag
async function loadFeature() {
  if (featureFlags.newDashboard) {
    const { NewDashboard } = await import('./features/NewDashboard');
    return NewDashboard;
  } else {
    const { OldDashboard } = await import('./features/OldDashboard');
    return OldDashboard;
  }
}

// Load on viewport intersection
function LazyImage({ src }) {
  const [imageSrc, setImageSrc] = useState(null);
  const imgRef = useRef(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      async ([entry]) => {
        if (entry.isIntersecting) {
          // Load image processing library when needed
          const { processImage } = await import('./imageUtils');
          const processed = await processImage(src);
          setImageSrc(processed);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );
    
    if (imgRef.current) observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, [src]);
  
  return <img ref={imgRef} src={imageSrc} />;
}

// Named export extraction
const loadChart = async (type) => {
  const chartModule = await import('chart.js');
  
  switch (type) {
    case 'bar':
      return new chartModule.BarChart();
    case 'line':
      return new chartModule.LineChart();
    case 'pie':
      return new chartModule.PieChart();
  }
};

// Webpack magic comments for optimization
const Dashboard = () => import(
  /* webpackChunkName: "dashboard" */
  /* webpackPreload: true */
  /* webpackExports: ["default", "config"] */
  './Dashboard'
);

Import Strategies

  • Eager: Bundle with main app (critical features)
  • Lazy: Load on route/component render
  • Prefetch: Load during idle time
  • Preload: Load in parallel with main bundle
  • On-interaction: Load on click/hover
  • On-visibility: Load when in viewport

Webpack Magic Comments

Comment Effect
webpackChunkName Name the chunk
webpackPrefetch Prefetch during idle
webpackPreload Preload immediately
webpackMode lazy, eager, weak
webpackExports Tree-shake exports

6. Navigation Guards Authentication Middleware

Guard Type Implementation Description Use Case
Global Guards router.beforeEach() Run before every route navigation, app-wide checks Authentication, analytics, scroll restoration
Route Guards beforeEnter Per-route validation before entering specific route Permission checks, data prefetching, redirects
Component Guards beforeRouteEnter Inside component, access to component instance Component-level validation, cleanup
Protected Routes meta: { requiresAuth } Route metadata for conditional access control Private pages, role-based access, subscription gates
Middleware Pattern middleware.js Composable middleware functions executed in sequence Complex auth flows, multi-step validation

Vue Router Guards

// Global guard
router.beforeEach(async (to, from, next) => {
  // Check authentication
  if (to.meta.requiresAuth) {
    const isAuthenticated = await checkAuth();
    if (!isAuthenticated) {
      return next({ name: 'Login', query: { redirect: to.fullPath } });
    }
  }
  
  // Check permissions
  if (to.meta.roles) {
    const userRole = getUserRole();
    if (!to.meta.roles.includes(userRole)) {
      return next({ name: 'Forbidden' });
    }
  }
  
  next();
});

// Route-level guard
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next();
      } else {
        next('/unauthorized');
      }
    }
  }
];

// Component guard
export default {
  beforeRouteEnter(to, from, next) {
    // Can't access `this` yet
    fetchUserData(to.params.id).then(user => {
      next(vm => {
        vm.user = user;
      });
    });
  },
  
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges) {
      const confirm = window.confirm('Discard changes?');
      next(confirm);
    } else {
      next();
    }
  }
};

React Router Guards

// Protected Route wrapper
function ProtectedRoute({ children }) {
  const { user } = useAuth();
  const location = useLocation();
  
  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

// Role-based guard
function RoleGuard({ roles, children }) {
  const { user } = useAuth();
  
  if (!roles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }
  
  return children;
}

// Usage
<Routes>
  <Route path="/" element={<Home />} />
  
  <Route path="/dashboard" element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } />
  
  <Route path="/admin" element={
    <ProtectedRoute>
      <RoleGuard roles={['admin']}>
        <Admin />
      </RoleGuard>
    </ProtectedRoute>
  } />
</Routes>

// Hook-based guard
function useAuthGuard() {
  const { user } = useAuth();
  const navigate = useNavigate();
  
  useEffect(() => {
    if (!user) {
      navigate('/login');
    }
  }, [user, navigate]);
  
  return user;
}

Example: Next.js Middleware

// middleware.js (Next.js 13+)
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('session')?.value;
  const { pathname } = request.nextUrl;
  
  // Protected routes
  if (pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
    
    // Verify token
    const user = verifyToken(token);
    if (!user) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
  
  // Admin routes
  if (pathname.startsWith('/admin')) {
    const user = verifyToken(token);
    if (user?.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', request.url));
    }
  }
  
  // Add custom headers
  const response = NextResponse.next();
  response.headers.set('x-middleware-cache', 'no-cache');
  
  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*']
};

Guard Best Practices

  • Server-side validation: Always verify on server, guards can be bypassed
  • Token refresh: Handle expired tokens, automatic refresh
  • Loading states: Show loading during async guard checks
  • Redirect chains: Avoid infinite redirects, track navigation history
  • Error handling: Graceful degradation on guard failures
Security Note: Client-side guards are for UX only. Always enforce authentication and authorization on the server. Guards prevent navigation, not API access.