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.

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.
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:
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.
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 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.
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.
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.
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:
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.
CSS modules scope classes so names don't collide across files. Create a components/badge.module.css file, then import it:
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 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.
Here's what to take away:
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.