Next.js course Β· Module 2: Routing and Layouts
Loading UI and layout.tsx
In this lesson6
In the Quantum Metropolis, every district has permanent infrastructure - lighting, navigation systems, information panels - that remains active regardless of who is currently in the zone. When a resident moves between sectors, holographic screens display a loading animation while the surrounding infrastructure remains unchanged.
In Next.js 16 App Router, these concepts have their equivalents: layout.tsx is the permanent infrastructure that wraps pages and is not re-rendered during navigation, and loading.tsx is the automatic loading indicator displayed during page transitions.
What is layout.tsx?
The layout.tsx file is one of the most important special files in the App Router. It defines a shared layout that wraps pages in a given segment and all its subsegments. The most important feature of a layout: it is not re-rendered when navigating between pages that share the same layout. It's like the infrastructure of a Quantum district - it stays in place, only the content inside changes.
Root Layout - The Required Main Layout
Every Next.js application must have a Root Layout in the app/layout.tsx file. This is the only layout that must contain the <html> and <body> tags:
1// app/layout.tsx - Root Layout
2import Link from 'next/link';
3
4export default function RootLayout({
5 children,
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <html lang="en">
11 <body>
12 <nav className="quantum-nav">
13 <Link href="/">Home</Link>
14 <Link href="/dashboard">Dashboard</Link>
15 <Link href="/settings">Settings</Link>
16 </nav>
17 <main>{children}</main>
18 <footer>Metropolis Quantum Β© 2150</footer>
19 </body>
20 </html>
21 );
22}Links in layouts are Link components from next/link. A plain <a href> would reload the whole page: the layout would mount again and lose its state.
The Root Layout is rendered once and remains active throughout the application's lifetime. Navigation between /dashboard and /settings will only change the {children} content - the navigation and footer will stay in place without re-rendering.
Nested Layouts
Layouts can be nested at any level of the folder structure. Each path segment can have its own layout.tsx:
1app/
2 βββ layout.tsx # Root Layout (html + body + navigation)
3 βββ page.tsx # Home /
4 βββ dashboard/
5 βββ layout.tsx # Dashboard Layout (sidebar + panel)
6 βββ page.tsx # /dashboard
7 βββ analytics/
8 β βββ page.tsx # /dashboard/analytics
9 βββ settings/
10 βββ page.tsx # /dashboard/settingsDashboard Layout nests inside the Root Layout:
1// app/dashboard/layout.tsx - Nested layout
2import Link from 'next/link';
3
4export default function DashboardLayout({
5 children,
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <div className="dashboard-container">
11 <aside className="sidebar">
12 <h2>Quantum Panel</h2>
13 <ul>
14 <li><Link href="/dashboard">Overview</Link></li>
15 <li><Link href="/dashboard/analytics">Analytics</Link></li>
16 <li><Link href="/dashboard/settings">Settings</Link></li>
17 </ul>
18 </aside>
19 <section className="content">
20 {children}
21 </section>
22 </div>
23 );
24}When the user navigates from /dashboard to /dashboard/analytics, the Root Layout and Dashboard Layout are not re-rendered. Only the {children} in the Dashboard Layout changes. This is a crucial performance optimization - the state of layout components (e.g., open sidebar, scroll position) is preserved.
Preserving Layout State During Navigation
One of the greatest advantages of layouts is state preservation during navigation. Imagine a control panel in the Quantum Metropolis - when you switch between sections, you don't want the entire interface to reload:
1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { useState } from 'react';
6
7export default function DashboardLayout({
8 children,
9}: {
10 children: React.ReactNode
11}) {
12 // This state WILL SURVIVE navigation between /dashboard/* pages!
13 const [sidebarOpen, setSidebarOpen] = useState(true);
14
15 return (
16 <div className="flex">
17 {sidebarOpen && (
18 <aside className="w-64 bg-gray-900 p-4">
19 <h2>Quantum Panel</h2>
20 <nav>
21 <Link href="/dashboard">Overview</Link>
22 <Link href="/dashboard/analytics">Analytics</Link>
23 </nav>
24 </aside>
25 )}
26 <div className="flex-1">
27 <button onClick={() => setSidebarOpen(!sidebarOpen)}>
28 {sidebarOpen ? 'Hide' : 'Show'} sidebar
29 </button>
30 {children}
31 </div>
32 </div>
33 );
34}If the user closes the sidebar on the /dashboard page and navigates to /dashboard/analytics, the sidebar will remain closed - the sidebarOpen state is not reset.
loading.tsx - Automatic Loading States
The loading.tsx file is a special file that Next.js automatically displays as a fallback while page content is loading. It's like a holographic loading screen in the Quantum Metropolis - it appears automatically when the system is preparing data.
How Does loading.tsx Work?
1// app/dashboard/loading.tsx
2export default function DashboardLoading() {
3 return (
4 <div className="loading-screen">
5 <div className="spinner"></div>
6 <p>Loading Quantum panel data...</p>
7 </div>
8 );
9}Next.js automatically wraps page.tsx in a <Suspense> boundary, using loading.tsx as the fallback:
1// This is what Next.js does "under the hood":
2<Layout>
3 <Suspense fallback={<Loading />}>
4 <Page />
5 </Suspense>
6</Layout>Thanks to this:
- The layout is displayed immediately (doesn't wait for page data)
- The loading component appears automatically during loading
- The page replaces the loading component when data is ready
- The user sees a responsive interface instead of a blank screen
loading.tsx at Different Levels
Each segment can have its own loading.tsx:
1app/
2 βββ loading.tsx # Loading for the home page
3 βββ dashboard/
4 β βββ loading.tsx # Loading for /dashboard/*
5 β βββ analytics/
6 β β βββ loading.tsx # Analytics-specific loading
7 β βββ settings/
8 β βββ page.tsx # Will use loading from /dashboard/template.tsx vs layout.tsx
Next.js offers one more special file - template.tsx. It looks identical to layout.tsx, but has one crucial difference: the template is re-mounted (a new instance is created) with each navigation.
1// app/dashboard/template.tsx
2// New instance with EVERY navigation!
3export default function DashboardTemplate({
4 children,
5}: {
6 children: React.ReactNode
7}) {
8 return (
9 <div className="animate-fadeIn">
10 {children}
11 </div>
12 );
13}When to use template.tsx instead of layout.tsx?
| Feature | layout.tsx | template.tsx |
|---|---|---|
| Re-rendering | NO - preserves state | YES - new instance |
| Component state | Preserved between navigations | Reset on navigation |
| useEffect | Does not re-run | Runs on every navigation |
| Entry animations | Do not repeat | Repeat on every entry |
Use cases for template.tsx:
- Entry/exit animations on pages (fade-in on every navigation)
- Page view logging (useEffect with analytics on every entry)
- Forms that should reset on navigation
Practical Applications
Navigation with Active State
1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { usePathname } from 'next/navigation';
6
7export default function DashboardLayout({
8 children,
9}: {
10 children: React.ReactNode
11}) {
12 const pathname = usePathname();
13
14 const links = [
15 { href: '/dashboard', label: 'Overview' },
16 { href: '/dashboard/analytics', label: 'Analytics' },
17 { href: '/dashboard/settings', label: 'Ustawienia' },
18 ];
19
20 return (
21 <div className="dashboard">
22 <nav className="dashboard-nav">
23 {links.map((link) => (
24 <Link
25 key={link.href}
26 href={link.href}
27 className={pathname === link.href ? 'active' : ''}
28 >
29 {link.label}
30 </Link>
31 ))}
32 </nav>
33 <main>{children}</main>
34 </div>
35 );
36}Summary
Layouts and loading UI are fundamental concepts of the App Router in Next.js 16:
- layout.tsx - a shared layout that wraps pages and is not re-rendered during navigation. It preserves component state.
- Root Layout - the required main layout with
<html>and<body>tags. - Nested layouts - each segment can have its own layout, creating a layout hierarchy.
- loading.tsx - an automatic loading indicator. Next.js wraps the page in
<Suspense>with loading as the fallback. - template.tsx - like a layout, but creates a new instance with each navigation. Ideal for animations and logging.
These mechanisms, inspired by the permanent infrastructure of the Quantum Metropolis districts, allow building efficient and responsive interfaces where the user never sees a blank screen, and navigation is smooth and instantaneous.
Code for this lesson: app/layout.tsx
1// Root Layout - Metropolis Quantum
2import { ReactNode } from 'react';
3import Link from 'next/link';
4
5interface RootLayoutProps {
6 children: ReactNode;
7}
8
9export const metadata = {
10 title: 'Metropolis Quantum',
11 description: 'Loading UI and layout.tsx',
12};
13
14export default function RootLayout({ children }: RootLayoutProps) {
15 return (
16 <html lang="en">
17 <body style={{ margin: 0, fontFamily: 'monospace', background: '#0f0f23', color: '#e0e0e0' }}>
18 <nav style={{
19 display: 'flex',
20 gap: '16px',
21 padding: '12px 20px',
22 background: '#1a1a2e',
23 borderBottom: '1px solid #64ffda',
24 }}>
25 <Link href="/" style={{ color: '#64ffda', textDecoration: 'none' }}>Home</Link>
26 <Link href="/dashboard" style={{ color: '#bd93f9', textDecoration: 'none' }}>Dashboard</Link>
27 <Link href="/settings" style={{ color: '#ff79c6', textDecoration: 'none' }}>Settings</Link>
28 </nav>
29 <main style={{ padding: '20px' }}>
30 {children}
31 </main>
32 <footer style={{
33 textAlign: 'center',
34 padding: '12px',
35 borderTop: '1px solid #16213e',
36 color: '#8892b0',
37 fontSize: '12px',
38 }}>
39 Metropolis Quantum © 2150
40 </footer>
41 </body>
42 </html>
43 );
44}Spotted a mistake in this lesson?