Learning Next.js - Components & Layouts
Episode 5 of 24

Learning Next.js - Components & Layouts

This episode covers reusable functional components, layouts and nested layouts, templates, shared layouts for dynamic segments, loading UI with Suspense, and styling with CSS modules, Tailwind CSS, and styled-components.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Routing from episode 4 determines the page structure; now it's time to fill those pages with clean UI. Components are the building blocks of an interface, and layouts are the way Next.js shares the same structure across pages.

Episode 5 covers reusable functional components, nested layouts and templates, shared layouts for dynamic segments, loading UI with the loading.tsx file, and three popular styling approaches: CSS modules, Tailwind CSS, and styled-components.

Creating Reusable Functional Components

The Principle of Reusability

A reusable component is a function that takes props and returns JSX. The key is avoiding duplicated code: if a button, card, or badge is used in many places, it deserves to be its own component. Here's an example of a simple badge component:

Reusable component
type BadgeProps = {
  label: string
  variant?: "primary" | "neutral"
}
 
export default function Badge({ label, variant = "neutral" }: BadgeProps) {
  return (
    <span className={variant === "primary" ? "badge-primary" : "badge-neutral"}>
      {label}
    </span>
  )
}

The Badge component above is reused with the label and variant props. Typing props with TypeScript keeps the component safe from calling errors — a wrong prop name is caught immediately by the compiler.

One indicator that a component deserves to be reusable: it appears in more than two places with the same structure. Before extracting, let the pattern emerge first — premature extraction adds abstraction that's hard to understand and maintain.

Component Folder Structure

Store components used across pages in the components/ folder. For components only used by one route, keep them close to that route, for example app/blog/[slug]/comments.tsx. This pattern keeps the proximity between a component and the page that uses it, and we'll explore it further in episode 18.

Naming consistency also matters: use PascalCase for component files and lowerCamelCase for helpers. Consistent conventions let a team guess file locations accurately.

Layouts, Nested Layouts, and Templates

Nested Layouts

Layouts can be nested. The root layout app/layout.tsx wraps the whole application, and then segment layouts wrap a subset of pages. Open any page and the root layout is always preserved — only the children part changes. This means the global navbar and footer don't need to be re-rendered when moving between pages.

Template: A Layout That Re-renders

Sometimes you need a frame that's new on every navigation, for example to reset scroll state or add an entrance animation. That's what templates are for: write app/blog/template.tsx with the same structure as a layout, but the components inside it are remounted every time the route changes. The difference from a layout: a layout is persistent, a template is created fresh.

Shared Layouts and Dynamic Segments

Layouts for Dynamic Segments

Layouts can also be shared across all values of a dynamic segment. The layout app/blog/layout.tsx wraps the /blog page, /blog/pengenalan-react, and all other articles — because a layout in the outermost segment file applies to all its descendants. A header with a link back to the blog list can go here so it always shows.

Loading UI with loading.tsx

The App Router supports streaming: create a file app/blog/loading.tsx and Next.js shows it as a placeholder while the page data is being processed on the server. With built-in Suspense, loading state doesn't need to be managed manually in every component:

Loading UI with Suspense
export default function BlogLoading() {
  return (
    <div className="grid gap-4">
      <div className="h-8 w-64 animate-pulse bg-gray-200" />
      <div className="h-4 w-full animate-pulse bg-gray-200" />
      <div className="h-4 w-full animate-pulse bg-gray-200" />
    </div>
  )
}

The skeleton above is displayed automatically while the blog page is waiting for data. The full streaming technique with Suspense is explained further in episode 23.

Styling with CSS Modules, Tailwind CSS, and styled-components

CSS Modules

CSS modules scope classes so names don't collide across files. Create a components/badge.module.css file, then import it:

Using a CSS module
import styles from "./badge.module.css"
 
export default function Badge({ label }) {
  return <span className={styles.primary}>{label}</span>
}

styles.primary produces a unique class name like badge_primary__3xK2b, so classes with the same name in other files won't override each other.

Tailwind CSS and styled-components

Tailwind CSS uses utility classes directly in JSX — the create-next-app scaffold already includes it. Its advantages: no separate CSS files to write and the final CSS size is automatically trimmed at build time. Meanwhile, styled-components uses tagged template literals to write styles inside JavaScript, great for dynamic prop-based themes. The choice depends on your project's needs: Tailwind for speed and consistency, styled-components for expressive conditional styling.

In practice, many projects combine approaches: Tailwind for quick styling across pages, CSS modules for components that need bespoke styles, and CSS variables for global themes. There's nothing wrong with using more than one, as long as the team is consistent about each one's role.

Closing

Here's what to take away:

  • Reusable components eliminate duplicated UI code.
  • The root layout and nested layouts wrap pages hierarchically.
  • Templates re-render on every navigation, unlike layouts.
  • The loading.tsx file provides streaming-based loading UI.
  • CSS modules scope classes per file.
  • Tailwind and styled-components are popular styling alternatives.

In the next episode, episode 6, we'll discuss data fetching and API routes — server-side data fetching in the App Router, static generation with generateStaticParams, Incremental Static Regeneration with revalidation, and building a backend API through route handlers. This is the heart of your fullstack application.

Learning Next.js - Components & Layouts | Learn Next.js