A Comprehensive Guide to App Routing in Next.js

Shyam Jith

--

Page Routing

  • Description: The core of Next.js routing is based on file conventions. Each JavaScript or TypeScript file within the pages directory becomes a route.
  • Example:
// pages/index.js
import Head from 'next/head';

export default function Home() {
return (
<div>
<Head>
<title>Welcome to my Next.js app</title>
</Head>
<h1>Hello, world!</h1>
</div>
);
}

Nested Routing

  • Description: To create nested routes, simply create subdirectories within the pages directory. Files within these subdirectories will be accessible as nested paths.
  • Example:
// pages/about/index.js
export default function About() {
return (
<div>
<h1>About Us</h1>
<p>This is the about page.</p>
</div>
);
}

Dynamic Routing

  • Description: Dynamic routes allow you to create pages that can handle different URL parameters. Use square brackets ([]) in your file names to indicate dynamic segments.
  • Example:
// pages/[id].js
import { useRouter } from 'next/router';

export default function Post({ id }) {
return (
<div>
<h1>Post {id}</h1>
<p>This is a dynamic post.</p>
</div>
);
}

export async function getServerSideProps(context) {
const { params } = context;
const id = params.id;
return { props: { id } };
}

Nested Dynamic Routing

  • Description: You can combine nested and dynamic routing. A file like [category]/[id].js can handle routes like /shoes/123 and /electronics/456.
  • Example:
// pages/[category]/[id].js
import { useRouter } from 'next/router';

export default function Product({ category, id }) {
return (
<div>
<h1>{category} - {id}</h1>
<p>This is a product page.</p>
</div>
);
}

export async function getServerSideProps(context) {
const { params } = context;
const category = params.category;
const id = params.id;
return { props: { category, id } };
}

Catch All Segments

  • Description: If you want a catch-all segment to be optional, use an asterisk (*) after the three dots.
  • Example:
// pages/[...slug]*.js
import { useRouter } from 'next/router';

export default function BlogPost({ slug }) {
return (
<div>
<h1>{slug.join('/')}</h1>
<p>This is a blog post.</p>
</div>
);
}

export async function getServerSideProps(context) {
const { params } = context;
const slug = params.slug;
return { props: { slug } };
}

Not Found Page

  • Description: Next.js automatically looks for a file named 404.js in the pages directory to render a 404 page when a route doesn't exist.
  • Example:
// pages/404.js
export default function NotFound() {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>The page you are looking for could not be found.</p>
</div>
);
}

File Collocation

  • Description: To create private routes that require authentication, place them in a private folder (e.g., pages/private).
  • Example:
// pages/private/login.js
// ... authentication logic ...

export default function Login() {
return (
<div>
<h1>Login</h1>
{/* ... login form ... */}
</div>
);
}

Route Groups

  • Description: Route groups allow you to organize related routes and apply common layouts or middleware to them. Create a directory within pages and place all related routes inside.
  • Example:
// pages/blog/index.js
export default function BlogIndex() {
return (
<div>
<h1>Blog</h1>
{/* ... list of blog posts ... */}
</div>
);
}

// pages/blog/[id].js
// ... blog post details ...

Nested Layouts

  • Description: Next.js supports nested layouts. Create a layout component in a subdirectory and place related pages within it. The layout component will be rendered around all pages in that directory.
  • Example:
// pages/blog/_app.js
import Layout from '../Layout';

export default function BlogApp({ Component, pageProps }) {
return <Layout><Component {...pageProps} /></Layout>;
}

// pages/blog/index.js
// ... blog index content ...

// pages/blog/[id].js
// ... blog post content ...

Route Group Layout

  • Description: You can combine route groups and layouts. Create a layout component within a route group directory to apply the layout to all pages in that group.
  • Example:
// pages/blog/_app.js
import Layout from './Layout';

export default function BlogApp({ Component, pageProps }) {
return <Layout><Component {...pageProps} /></Layout>;
}

// pages/blog/Layout.js
// ... layout for blog pages ...

Routing Metadata

  • Title Metadata:
// pages/index.js
import Head from 'next/head';

export default function Home() {
return (
<div>
<Head>
<title>Welcome to my Next.js app</title>
</Head>
<h1>Hello, world!</h1>
</div>
);
}
  • Link Component:
// pages/index.js
import Link from 'next/link';

export default function Home() {
return (
<div>
<Link href="/about">About</Link>
</div>
);
}
  • Active Links:
// pages/index.js
import Link from 'next/link';
import { useRouter } from 'next/router';

export default function Home() {
const router = useRouter();

return (
<div>
<nav>
<Link href="/" className={router.pathname === '/' ? 'active' : ''}>Home</Link>
<Link href="/about" className={router.pathname === '/about' ? 'active' : ''}>About</Link>
</nav>
</div>
);
}
  • Navigating Pragmatically:
// pages/index.js
import { useRouter } from 'next/router';

export default function Home() {
const router = useRouter();

const handleClick = () => {
router.push('/about');
};

return (
<div>
<button onClick={handleClick}>Go to About</button>
</div>
);
}

Template

  • Description: Create a reusable template component to share common elements across multiple pages.
  • Example:
// components/Template.js
export default function Template({ children }) {
return (
<div className="container">
<header>
{/* ... header content ... */}
</header>
<main>
{children}
</main>
<footer>
{/* ... footer content ... */}
</footer>
</div>
);
}

Loading UI

  • Description: Show a loading indicator while pages are being fetched or rendered.
  • Example:
// pages/index.js
import { useState, useEffect } from 'react';

export default function Home() {
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
// ... fetch data ...
setIsLoading(false);
};

fetchData();
}, []);

return (
<div>
{isLoading ? (
<p>Loading...</p>
) : (
<h1>Hello, world!</h1>
)}
</div>
);
}

Error Handling

  • Description: Use try...catch blocks or Next.js's built-in error handling mechanisms to catch and display errors gracefully.
  • Example:
// pages/index.js
import { useState, useEffect } from 'react';

export default function Home() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
} catch (error) {
setError(error);
}
};

fetchData();
}, []);

return (
<div>
{error ? (
<p>Error: {error.message}</p>
) : data ? (
<p>Data: {JSON.stringify(data)}</p>
) : (
<p>Loading...</p>
)}
</div>
);
}

Recovering from Error

  • Description: Provide a way for users to recover from errors, such as retrying an action or navigating to a different page.
  • Example:
// pages/index.js
// ... error handling code ...

return (
<div>
{error ? (
<div>
<p>Error: {error.message}</p>
<button onClick={() => fetchData()}>Retry</button>
</div>
) : data ? (
// ...
) : (
// ...
)}
</div>
);

Handling Errors in Nested Routes

  • Description: Handle errors in nested routes by using error boundaries or custom error handling logic.
  • Example:
import { ErrorBoundary } from 'react-error-boundary';

export default function MyComponent() {
return (
<ErrorBoundary fallback={<p>Something went wrong!</p>}>
{/* ... nested components ... */}
</ErrorBoundary>
);
}

Handling Errors in Layouts

  • Description: Handle errors in layouts to prevent the entire application from crashing.
  • Example:
import { ErrorBoundary } from 'react-error-boundary';

export default function Layout({ children }) {
return (
<ErrorBoundary fallback={<p>Something went wrong!</p>}>
<div>
{/* ... header ... */}
{children}
{/* ... footer ... */}
</div>
</ErrorBoundary>
);
}

Parallel Routes

  • Description: Render multiple routes in parallel using the getStaticProps or getServerSideProps methods.
  • Example:
// pages/index.js
export async function getStaticProps() {
const blogPosts = await fetch('https://api.example.com/blog-posts').then(res => res.json());
const products = await fetch('https://api.example.com/products').then(res => res.json());
return { props: { blogPosts, products } };
}

// pages/index.js
export default function Home({ blogPosts, products }) {
// ... render blog posts and products ...
}

Conditional Routes

  • Description: Render different content based on conditions using if statements or ternary operators.
  • Example:
import { useRouter } from 'next/router';

export default function MyComponent() {
const router = useRouter();

return router.pathname === '/about' ? <AboutPage /> : <HomePage />;
}

Intercepting Routes

  • Description: Use custom server-side rendering logic or middleware to intercept routes and modify their behavior.
  • Example:
// pages/_app.js
import { useRouter } from 'next/router';

export default function MyApp({ Component, pageProps }) {
const router = useRouter();

if (router.pathname === '/private') {
// Check authentication and redirect if necessary
if (!isAuthenticated) {
router.push('/login');
return null;
}
}

return <Component {...pageProps} />;
}

Parallel Intercepting Routes

  • Description: Intercept multiple routes in parallel using middleware or custom server-side rendering logic.
  • Example:
// pages/_app.js
import { useRouter } from 'next/router';

export default function MyApp({ Component, pageProps }) {
const router = useRouter();

// Intercept multiple routes
if (router.pathname.startsWith('/blog')) {
// Apply middleware for blog routes
} else if (router.pathname.startsWith('/products')) {
// Apply middleware for product routes
}

return <Component {...pageProps} />;
}

Conclusion

Next.js offers a robust and flexible routing system that empowers developers to create dynamic and scalable web applications. By understanding and effectively utilizing the concepts and techniques discussed in this guide, you can build complex routing structures, manage state, and optimize performance in your Next.js projects.

--

--

No responses yet