Every Next.js application has one special layout that's absolutely required—the root layout. Located at app/layout.tsx, it wraps your entire application and is the only place where you can configure the <html> and <body> tags. This is where you set up global styles, fonts, metadata, analytics, and anything else that should apply to your entire app. Let's master this foundational component!
What Is the Root Layout?
The root layout is the top-level layout file in your application:
- Location:
app/layout.tsx - Required: Your app won't run without it
- Wraps everything: All pages and nested layouts
- Controls HTML structure: Only place to define
<html>and<body>
Root Layout in Project Structure
The root layout is the foundation of your app
Select a file or folder to see details
Required Tags
The root layout MUST include:
<html>tag<body>tag
Without these, Next.js will throw an error. No other layout can have these tags.
Basic Root Layout Structure
Here's the simplest valid root layout:
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}This is the minimum required structure. Let's break it down:
<html lang="en">
The HTML tag wraps everything. The lang attribute is important for accessibility and SEO.
<body>
The body tag contains all visible content. You can add classes here for styling.
{children}
All pages and nested layouts render here. This prop is automatically provided.
Complete Root Layout Example
A production-ready root layout with all common features:
import { Inter } from 'next/font/google';
import './globals.css';
import { Metadata } from 'next';
// Font configuration
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
// Global metadata
export const metadata: Metadata = {
title: {
default: 'My App - Best App Ever',
template: '%s | My App', // Page titles will use this template
},
description: 'Build amazing things with My App',
keywords: ['nextjs', 'react', 'web app'],
authors: [{ name: 'Your Name' }],
creator: 'Your Company',
publisher: 'Your Company',
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://myapp.com',
title: 'My App',
description: 'Build amazing things with My App',
siteName: 'My App',
images: [
{
url: 'https://myapp.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'My App',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'My App',
description: 'Build amazing things with My App',
images: ['https://myapp.com/twitter-image.jpg'],
creator: '@myapp',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
icons: {
icon: '/favicon.ico',
shortcut: '/favicon-16x16.png',
apple: '/apple-touch-icon.png',
},
manifest: '/site.webmanifest',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.variable}>
<body className="min-h-screen bg-white text-gray-900 antialiased">
{children}
</body>
</html>
);
}Let's understand each part:
Global Metadata Configuration
The metadata export defines default SEO and social sharing information:
Basic Metadata
export const metadata: Metadata = {
// Page title configuration
title: {
default: 'My App', // Used when page doesn't set title
template: '%s | My App', // Template for page titles
},
// Meta description
description: 'Build amazing things with My App',
// Keywords for SEO
keywords: ['nextjs', 'react', 'typescript'],
// Author information
authors: [
{ name: 'Your Name', url: 'https://yoursite.com' }
],
};How Title Templates Work
// Root layout
export const metadata = {
title: {
template: '%s | My App',
default: 'My App',
},
};
// In app/about/page.tsx
export const metadata = {
title: 'About Us',
};
// Result: "About Us | My App"
// In app/contact/page.tsx
export const metadata = {
title: 'Contact',
};
// Result: "Contact | My App"
// Homepage (no title set)
// Result: "My App" (uses default)Open Graph (Social Media)
export const metadata = {
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://myapp.com',
siteName: 'My App',
title: 'My App',
description: 'Build amazing things',
images: [
{
url: 'https://myapp.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'My App Preview',
},
],
},
};Twitter Cards
export const metadata = {
twitter: {
card: 'summary_large_image', // or 'summary' for small image
site: '@myapp', // Your Twitter handle
creator: '@yourname',
title: 'My App',
description: 'Build amazing things',
images: ['https://myapp.com/twitter-image.jpg'],
},
};Icons and Manifest
export const metadata = {
// Favicon and app icons
icons: {
icon: '/favicon.ico',
shortcut: '/favicon-16x16.png',
apple: '/apple-touch-icon.png',
other: [
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
url: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
url: '/favicon-16x16.png',
},
],
},
// Web app manifest
manifest: '/site.webmanifest',
};🎯 Metadata Priority
Metadata defined in pages overrides metadata from layouts. Use the root layout for defaults, override in pages as needed.
Font Optimization
Next.js has built-in font optimization. Import fonts in the root layout:
Using Google Fonts
import { Inter, Roboto_Mono } from 'next/font/google';
// Configure fonts
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body className={inter.className}>
{children}
</body>
</html>
);
}Using in CSS
/* Use the CSS variables */
body {
font-family: var(--font-inter), sans-serif;
}
code {
font-family: var(--font-roboto-mono), monospace;
}Using Local Fonts
import localFont from 'next/font/local';
const myFont = localFont({
src: './fonts/my-font.woff2',
display: 'swap',
variable: '--font-my-font',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={myFont.variable}>
<body className={myFont.className}>
{children}
</body>
</html>
);
}Font Optimization Benefits
- Automatic font subsetting
- Self-hosted fonts (no external requests)
- Zero layout shift
- Automatic font loading optimization
Global Styles
Import global CSS in the root layout:
import './globals.css'; // Import global styles
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Typical globals.css Structure
/* Tailwind directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* CSS variables for theming */
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-accent: #10b981;
--spacing-unit: 8px;
}
/* Global resets */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Global styles */
html {
scroll-behavior: smooth;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Custom utilities */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--color-background: #1a1a1a;
--color-text: #ffffff;
}
}Customizing HTML and Body Tags
Adding Classes
export default function RootLayout({ children }) {
return (
<html lang="en" className="scroll-smooth">
<body className="min-h-screen bg-gray-50 text-gray-900 antialiased">
{children}
</body>
</html>
);
}Conditional Classes
import { cookies } from 'next/headers';
export default function RootLayout({ children }) {
const theme = cookies().get('theme')?.value || 'light';
return (
<html lang="en" className={theme}>
<body className={theme === 'dark' ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'}>
{children}
</body>
</html>
);
}Adding Data Attributes
export default function RootLayout({ children }) {
return (
<html lang="en" data-theme="light">
<body data-environment={process.env.NODE_ENV}>
{children}
</body>
</html>
);
}Don't Add <head> Manually
Next.js manages the <head> automatically based on your metadata exports. Manual <head> tags will be ignored.
Adding Providers and Wrappers
The root layout is where you add global providers:
Context Providers
import { AuthProvider } from '@/contexts/AuthContext';
import { ThemeProvider } from '@/contexts/ThemeContext';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<AuthProvider>
<ThemeProvider>
{children}
</ThemeProvider>
</AuthProvider>
</body>
</html>
);
}Analytics
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}Third-Party Scripts
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
{/* Google Analytics */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_ID');
`}
</Script>
</body>
</html>
);
}⚡ Script Strategies
beforeInteractive- Critical scripts (polyfills)afterInteractive- Analytics, ads (default)lazyOnload- Non-critical scripts (chat widgets)
Viewport and Mobile Configuration
Configure viewport settings for mobile:
import { Viewport } from 'next';
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
userScalable: false,
// themeColor: '#ffffff', // Or use media queries
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#000000' },
],
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Common Root Layout Patterns
1. Production-Ready Root Layout
Production-Ready Root Layout
Complete setup with fonts, metadata, and analytics
Output Preview
2. Multi-Language Support
export default function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: { lang: string };
}) {
return (
<html lang={params.lang} dir={params.lang === 'ar' ? 'rtl' : 'ltr'}>
<body>{children}</body>
</html>
);
}3. Progressive Web App
export const metadata = {
manifest: '/manifest.json',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'My App',
},
formatDetection: {
telephone: false,
},
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Root Layout Best Practices
1. Keep It Minimal
Don't put page-specific logic in the root layout:
// ✅ Good: Minimal, structural
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
// ❌ Bad: Too much logic
export default function RootLayout({ children }) {
const user = fetchUser(); // Don't fetch in root layout
const posts = fetchPosts(); // This affects all pages
// ...complex logic
}2. Server Component by Default
Keep the root layout as a Server Component unless absolutely necessary:
// ✅ Prefer Server Component (default)
export default function RootLayout({ children }) {
return <html><body>{children}</body></html>;
}
// Only use client if absolutely needed
// Making root layout a client component affects the entire app!3. Use Proper Metadata
// ✅ Complete metadata
export const metadata = {
title: { template: '%s | My App', default: 'My App' },
description: 'Comprehensive description',
keywords: ['relevant', 'keywords'],
openGraph: { /* ... */ },
twitter: { /* ... */ },
};
// ❌ Incomplete metadata
export const metadata = {
title: 'My App', // Missing template and other fields
};4. Optimize Fonts
// ✅ Use Next.js font optimization
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
// ❌ Don't use external font links
// <link href="https://fonts.googleapis.com/..." />Common Issues
Issue 1: Missing Required Tags
Error: "The root layout must contain html and body tags"
Solution: Ensure your root layout includes both tags:
// ✅ Correct
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Issue 2: Metadata Not Showing
Problem: Metadata not appearing in page source
Solutions:
- Check metadata is exported (not default export)
- Verify metadata object structure
- Clear browser cache
- Check build output
Issue 3: Fonts Not Loading
Problem: Custom fonts not appearing
Solutions:
- Apply font className to body tag
- Check font import path
- Verify font file exists
- Check CSS variable usage
Key Takeaways
- Root layout is required - app won't run without it
- Must include <html> and <body> - only place to define them
- Export metadata for SEO - defines global defaults
- Use font optimization - import fonts properly
- Import global styles - in root layout
- Add providers here - auth, theme, analytics
- Keep it minimal - structural only, no page logic
- Server Component preferred - unless you need interactivity
What's Next?
You've mastered the root layout—the foundation of your Next.js app! Now it's time to explore nested layouts. You'll learn how to create layouts for specific sections of your application, compose layouts together, and build sophisticated layout hierarchies.
Nested layouts let you have different UI for different parts of your app—like a blog with a sidebar or a dashboard with navigation—while still sharing the root layout's global configuration. Let's dive in!
🏗️ Foundation Built!
The root layout is the foundation of every Next.js app. Take time to set it up properly with good metadata, optimized fonts, and clean structure. This investment pays off throughout your entire project!