The problem: a site that doesn't leverage the App Router
You have a Next.js site using the old pages router. Every page reloads fully. Loading spinners appear randomly. An error in one component breaks the entire screen. And to organize routes you end up with weird folder names.
We, at Meteora Web, have seen dozens of such projects. Sites that could fly but crawled due to poor structure. Next.js App Router solves all this — but you need to understand it deeply, not just skim a generic tutorial.
In this guide we explain why layouts, loading, error, and route groups are game-changers and how to use them in your projects right now.
Layout: the backbone of navigation
A layout in App Router is a component that wraps child pages and does not re-render when you navigate between them. That means sidebar, header, footer — everything shared — stays intact. The user perceives the transition as instant.
To create a layout, just add a layout.tsx (or .js) file in the route folder. Next.js nests them automatically: each layout wraps the layouts of subfolders.
// app/layout.tsx — root layout
import type { ReactNode } from 'react';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<header>Global header</header>
<main>{children}</main>
<footer>Global footer</footer>
</body>
</html>
);
}
You can nest layouts for specific sections. For example, a dashboard with sidebar and an admin area with a different layout.
// app/dashboard/layout.tsx
import Sidebar from '@/components/Sidebar';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<Sidebar />
<main>{children}</main>
</div>
);
}
Common mistake: thinking the layout reloads when you change pages. It doesn't: Next.js keeps the layout state as long as you stay within the same hierarchy. A counter in a layout won't reset.
Loading: not just a spinner
The loading.tsx file is a component Next.js shows instantly while the page (or segment) is loading data. Thanks to Server Components and streaming, the user sees something right away, even if the main content is still arriving.
// app/products/loading.tsx
export default function Loading() {
return (
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="h-48 bg-gray-200 animate-pulse rounded" />
))}
</div>
);
}
The loading appears immediately (before the page finishes loading) and then is automatically replaced. It's perfect for pages that fetch slow data (APIs, databases).
Practical tip: don't use one global loading file. Put different loading components for each section. That way the user understands exactly what's loading. A generic spinner for the whole e-commerce is worse than nothing.
Error: isolate failures
When a Server Component throws an exception, everything breaks. With error.tsx you delimit the problem to a specific segment. The error is caught and you show a fallback UI, while the rest of the site continues working.
// app/products/error.tsx
'use client';
export default function ErrorBoundary({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="text-center p-8">
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button
onClick={() => reset()}
className="mt-4 bg-blue-600 text-white px-4 py-2 rounded"
>
Try again
</button>
</div>
);
}
The error.tsx component must be a Client Component ('use client') because it has state and event handling. Next.js renders it client-side when an error occurs in the parent Server Component.
Important note: the error does not propagate upward. If you put an error.tsx in app/products/error.tsx, it only catches errors in /products and its sub-pages. The rest of the site (e.g., homepage) remains intact.
For global errors (e.g., 404), use the not-found.tsx file at root level.
Route Groups: organize without affecting the URL
Route groups are folders whose name is enclosed in parentheses (name). They don't affect the URL but allow you to logically group routes and apply different layouts.
app/
├── (marketing)/
│ ├── layout.tsx
│ ├── page.tsx → /
│ └── about/page.tsx → /about
├── (shop)/
│ ├── layout.tsx
│ ├── products/page.tsx → /products
│ └── cart/page.tsx → /cart
└── (auth)/
├── login/page.tsx → /login
└── register/page.tsx → /register
Each group can have its own layout, loading, and error. The final URL is determined by the path inside the group, ignoring the parentheses.
Real-world scenario: a site with a marketing section (hero, CTA layout) and a dashboard section (sidebar layout). With route groups you don't need to hack routing logic: create groups and assign different layouts.
Warning: you cannot have two route groups that define the same path. For example, (marketing)/about and (shop)/about would conflict because both resolve to /about.
Putting it all together: a concrete example
Imagine an admin application with two sections: a public one (landing, contact) and a private one (dashboard, users).
app/
├── (public)/
│ ├── layout.tsx // marketing layout
│ ├── page.tsx // homepage
│ └── contact/page.tsx
├── (private)/
│ ├── layout.tsx // layout with sidebar
│ ├── loading.tsx // spinner for the whole private section
│ ├── error.tsx // global error for private section
│ ├── dashboard/page.tsx
│ └── users/
│ ├── page.tsx
│ ├── loading.tsx // spinner specific to users
│ └── error.tsx // error specific to users
This way, the public section has a lightweight layout, while the private one loads the sidebar and has dedicated loading/error. Data from the dashboard won't block navigation between public pages.
What to do now
- Migrate an existing project: create a new Next.js project with
create-next-app@latestand choose App Router. - Identify common sections of your site (header, footer, sidebar) and turn them into layouts.
- Add loading.tsx for every page that fetches data. Use skeleton placeholders, not the classic spinner.
- Add error.tsx in every route folder that can fail (lists, details, forms).
- Reorganize folders with route groups to separate areas with different layouts (public, private, admin).
- Test the behavior: navigate between pages and verify that layouts don't reload and errors are isolated.
For deeper dive, check the official Next.js routing documentation. And remember: a well-structured application is not a luxury — it's the difference between a site that sells and one that frustrates users.
Sponsored Protocol