Build routes from folders, share UI with layouts, and nest layouts so each section of your app keeps its own shell.
Why: in Next.js you create a URL by creating a folder and putting a page.tsx in it. A route stays private until that page.tsx (or route.ts) exists — empty folders are not reachable.
// Folder structure -> URL
// app/page.tsx -> /
// app/blog/page.tsx -> /blog
// app/blog/authors/page.tsx -> /blog/authors
// app/blog/page.tsx
export default function BlogPage() {
return <h1>The Blog</h1>
}Why: a layout is UI shared by many pages. The root layout (app/layout.tsx) is required and must render the <html> and <body> tags. Its children prop is where the active page gets slotted in.
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<header>My Site</header>
<main>{children}</main>
</body>
</html>
)
}Why: add a layout.tsx inside any folder and it wraps only that section. On navigation a layout does NOT re-render — it keeps its state and stays interactive while the page below it swaps. Note: layouts nest, so the blog layout renders inside the root layout.
// app/blog/layout.tsx — wraps /blog and everything under it
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<section>
<nav>Blog navigation</nav>
{children}
</section>
)
}Why: pages are Server Components by default, so you can make the component async and await data right inside it — no useEffect, no loading library. The HTML arrives already filled in. Note: a Server Component runs on the server, so this code never ships to the browser.
// app/blog/page.tsx
import { getPosts } from '@/lib/posts'
export default async function BlogPage() {
const posts = await getPosts()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}