How pages and layout work in Nextjs App Router?
In Next.js 14, routing is handled differently compared to older versions. The App Router uses file-based routing, where the structure of your files and folders directly translates into routes. This tutorial explains how page.tsx
and layout.tsx
work together to render pages, how routing works within the recipes
folder, how to set up dynamic routes, and how nested layouts work with the dashboard
.
How 'page.tsx' and 'layout.tsx' Work Together
In the App Router, layout.tsx
wraps all of your pages and controls shared content like headers and footers. Each individual page is defined by its own page.tsx
, which provides the specific content for that route. Let’s look at an example with the home page.
// src/app/layout.tsx
export const metadata = { title: 'My Recipe App' };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>{children}</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
);
}
// src/app/page.tsx
export default function HomePage() {
return (
<section>
<h2>Welcome to the Recipe App</h2>
<p>Find the best recipes and share your own!</p>
</section>
);
}
When you visit the home page (/
), layout.tsx
provides the overall structure, including the header and footer, while page.tsx
renders the page-specific content (in this case, the welcome message).
Final HTML output:
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>
<section>
<h2>Welcome to the Recipe App</h2>
<p>Find the best recipes and share your own!</p>
</section>
</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
Routing within the 'recipes' Folder
In Next.js 14, the structure of your folders directly determines your app’s routes. For example, creating a folder named recipes
with a page.tsx
file inside automatically creates a /recipes
route.
// src/app/recipes/page.tsx
export default function RecipesPage() {
return (
<section>
<h2>All Recipes</h2>
<ul>
<li><a href="/recipes/chocolate-cake">Chocolate Cake</a></li>
<li><a href="/recipes/pasta">Pasta</a></li>
</ul>
</section>
);
}
Visiting /recipes
will display the list of recipes.
Final HTML output:
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>
<section>
<h2>All Recipes</h2>
<ul>
<li><a href="/recipes/chocolate-cake">Chocolate Cake</a></li>
<li><a href="/recipes/pasta">Pasta</a></li>
</ul>
</section>
</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
Dynamic Route with '[id]'
To display individual recipes based on an ID, you can use dynamic routes. Next.js automatically matches routes based on the folder and filename.
// src/app/recipes/[id]/page.tsx
export default function RecipeDetail({ params }: { params: { id: string } }) {
return (
<section>
<h2>{params.id.replace('-', ' ')} Recipe</h2>
<p>Here's how to make the best {params.id.replace('-', ' ')}!</p>
</section>
);
}
Visiting /recipes/chocolate-cake
will render the details of the chocolate cake recipe. The params.id
extracts the URL parameter and uses it to display the correct recipe.
Final HTML output:
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>
<section>
<h2>Chocolate Cake Recipe</h2>
<p>Here's how to make the best Chocolate Cake!</p>
</section>
</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
Certainly! Here's an example of a dynamic route with more than one slug for the Recipe App, where you handle recipes based on both the category and the recipe ID.
Dynamic Route with Multiple Slugs ('[category]/[id]')
In this example, we will create a route that allows users to visit a recipe by both its category (like "desserts" or "main-course") and its ID (like "chocolate-cake"). This is useful when you want to have organized URLs that are descriptive, such as /recipes/desserts/chocolate-cake
.
The folder structure will look like this:
src/
app/
recipes/
[category]/
[id]/
page.tsx
Dynamic Route Code Example:
// src/app/recipes/[category]/[id]/page.tsx
export default function RecipeDetail({
params,
}: {
params: { category: string; id: string };
}) {
const categoryFormatted = params.category.replace('-', ' ');
const recipeName = params.id.replace('-', ' ');
return (
<section>
<h2>{recipeName} Recipe ({categoryFormatted})</h2>
<p>Here's how to make the best {recipeName} in the {categoryFormatted} category!</p>
</section>
);
}
Explanation:
params.category
captures the dynamic category from the URL.params.id
captures the dynamic recipe ID.- Both the
category
andid
are processed using.replace('-', ' ')
to make the slugs more human-readable (replacing hyphens with spaces).
Example URL and Output:
If you visit /recipes/desserts/chocolate-cake
, the component will render the chocolate cake recipe in the desserts category.
Final HTML output for /recipes/desserts/chocolate-cake
:
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>
<section>
<h2>Chocolate Cake Recipe (Desserts)</h2>
<p>Here's how to make the best Chocolate Cake in the Desserts category!</p>
</section>
</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
This dynamic route allows you to create descriptive and organized URLs that show both the recipe category and the specific recipe name.
Nested Layout
In the dashboard
folder, you can create a layout specific to the dashboard section of the app. The layout.tsx
in the dashboard
folder will wrap all dashboard-related pages, while the global layout.tsx
continues to wrap the entire app.
// src/app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<aside>
<a href="/dashboard">Dashboard Home</a> | <a href="/dashboard/add-recipe">Add Recipe</a>
</aside>
<div>{children}</div>
</div>
);
}
// src/app/dashboard/page.tsx
export default function DashboardHome() {
return (
<section>
<h2>Dashboard</h2>
<p>Manage your recipes here.</p>
</section>
);
}
In this setup, when you visit /dashboard
, both the global layout from src/app/layout.tsx
and the specific layout from src/app/dashboard/layout.tsx
will be combined to create the final page.
Final HTML output:
<html lang="en">
<body>
<header>
<h1>Recipe App</h1>
<nav>
<a href="/">Home</a> | <a href="/recipes">Recipes</a> | <a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>
<div>
<aside>
<a href="/dashboard">Dashboard Home</a> | <a href="/dashboard/add-recipe">Add Recipe</a>
</aside>
<div>
<section>
<h2>Dashboard</h2>
<p>Manage your recipes here.</p>
</section>
</div>
</div>
</main>
<footer>© 2024 Recipe App</footer>
</body>
</html>
In this output, you can see how both the global layout and the specific dashboard/layout.tsx
are applied together to structure the dashboard page.