
Next.js File Conventions
š„ Don't let your users stare at empty screens
Next.js uses special file names to define routes, layouts, and UI states. Some of these files can literally save your users from frustration.
ā
page.tsx
ā
layout.tsx
ā
loading.tsx
ā
error.tsx
ā
not-found.tsx
ā
template.tsx
ā
default.tsx
ā
route.tsx
ā
middleware.ts
ā
Metadata files
#nextjs #react #approuter #routing #webdev #frontend #coding #tips
| File | Description |
|---|---|
page.tsx | The UI for a route segment. Makes a path public. |
layout.tsx | Shared UI that wraps child segments and persists. |
loading.tsx | Instant loading UI shown while the segment loads. |
error.tsx | Error boundary UI for a segment. |
not-found.tsx | UI shown when notFound() is called. |
template.tsx | Like layout, but remounts on navigation. |
default.tsx | Fallback for parallel routes when no match. |
route.tsx | API endpoint for a route segment. |
middleware.ts | Run code before a request completes. |
icon.png | Favicon and app icons. |
opengraph-image.png | Open Graph image for social sharing. |
sitemap.ts | Generate a sitemap for search engines. |
robots.ts | Generate a robots.txt file. |
manifest.ts | Generate a web app manifest. |
page.tsx
Define the UI for a route. A route is only public when it has a page.
app
āāā page.tsx ā /
āāā blog
āāā page.tsx ā /blog
āāā [slug]
āāā page.tsx ā /blog/hello// app/blog/page.tsx ā /blog
export default function BlogPage() {
return <h1>Blog</h1>;
}Pages receive params and searchParams as props.
params are the dynamic segments of the URL.
app
āāā page.tsx ā /
āāā blog
āāā page.tsx ā /blog
āāā [slug]
āāā page.tsx ā /blog/helloexport default async function Page({ params, searchParams }) {
const { id } = await params;
const { q } = await searchParams;
}searchParams are the query parameters.
https://example.com/blog/hello?q=searchexport default async function Page({ params, searchParams }) {
const { id } = await params;
const { q } = await searchParams;
}layout.tsx
Shared UI that wraps a segment and its children. Layouts preserve state and do not re-render on navigation.
app
āāā layout.tsx # root layout (required)
āāā dashboard
āāā layout.tsx # wraps /dashboard/*
āāā page.tsx// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
return (
<section>
<Sidebar />
{children}
</section>
);
}The root layout.tsx is required and must include html and body.
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}loading.tsx
Wraps the page in a React Suspense boundary. Shown instantly while the segment loads.
app
āāā dashboard
āāā loading.tsx # shown while dashboard/page.tsx loads
āāā page.tsx// app/dashboard/loading.tsx
export default function Loading() {
return <Spinner />;
}error.tsx
Catches errors in the segment and its children. Must be a client component.
app
āāā global-error.tsx # catches errors in the root layout
āāā dashboard
āāā error.tsx # catches errors in /dashboard/*
āāā page.tsx"use client";
export default function Error({ error, reset }) {
return (
<div>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}Use global-error.tsx to catch errors in the root layout.
not-found.tsx
Rendered when notFound() is called or a URL matches nothing.
app
āāā not-found.tsx # root 404 page
āāā blog
āāā not-found.tsx # 404 for /blog/*
āāā [slug]
āāā page.tsximport { notFound } from "next/navigation";
export default async function Post({ params }) {
const post = await getPost((await params).id);
if (!post) notFound(); // renders not-found.tsx
}// app/not-found.tsx
export default function NotFound() {
return <h1>404 - Page not found</h1>;
}template.tsx
Like a layout, but creates a new instance on navigation. State is not preserved.
app
āāā template.tsx # remounts on every navigation
āāā page.tsx// app/template.tsx
export default function Template({ children }) {
return <div>{children}</div>;
}Use it for enter/exit animations or per-page effects.
default.tsx
Fallback UI for parallel routes when the current URL doesn't match a slot.
app
āāā @modal
ā āāā default.tsx # fallback when no modal is active
ā āāā login
ā āāā page.tsx
āāā layout.tsx # receives { modal } as a prop// app/@modal/default.tsx
export default function Default() {
return null;
}route.tsx
Create an API endpoint. Cannot coexist with page.tsx at the same segment.
app
āāā api
āāā users
āāā route.ts ā /api/users// app/api/users/route.ts ā /api/users
import { NextResponse } from "next/server";
export async function GET() {
const users = await getUsers();
return NextResponse.json(users);
}
export async function POST(request) {
const body = await request.json();
return NextResponse.json(body, { status: 201 });
}middleware.ts
Run code before a request completes: redirects, rewrites, auth checks.
project-root
āāā middleware.ts # root of the project, next to app/
āāā app
āāā page.tsx// middleware.ts (project root)
import { NextResponse } from "next/server";
export function middleware(request) {
if (!request.cookies.has("session")) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
export const config = {
matcher: ["/dashboard/:path*"],
};Metadata files
Special files that map to SEO and browser features.
app
āāā icon.png # favicon
āāā apple-icon.png # Apple touch icon
āāā opengraph-image.png # social sharing image
āāā sitemap.ts # generated sitemap
āāā robots.ts # generated robots.txt
āāā manifest.ts # web app manifest// app/sitemap.ts
export default function sitemap() {
return [{ url: "https://example.com", lastModified: new Date() }];
}// app/robots.ts
export default function robots() {
return { rules: { userAgent: "*", allow: "/" } };
}Image files like icon.png, apple-icon.png, and opengraph-image.png are picked up automatically.
Full-Stack AI Developer Roadmap
From HTML & CSS to working with AI models, all in one structured roadmap.