ā¤ Like
šŸ”– Save
šŸ”— Share
Eric Hu
Eric Hu

Next.js File Conventions

@thedevspaceio

šŸ”„ 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


FileDescription
page.tsxThe UI for a route segment. Makes a path public.
layout.tsxShared UI that wraps child segments and persists.
loading.tsxInstant loading UI shown while the segment loads.
error.tsxError boundary UI for a segment.
not-found.tsxUI shown when notFound() is called.
template.tsxLike layout, but remounts on navigation.
default.tsxFallback for parallel routes when no match.
route.tsxAPI endpoint for a route segment.
middleware.tsRun code before a request completes.
icon.pngFavicon and app icons.
opengraph-image.pngOpen Graph image for social sharing.
sitemap.tsGenerate a sitemap for search engines.
robots.tsGenerate a robots.txt file.
manifest.tsGenerate a web app manifest.

page.tsx

Define the UI for a route. A route is only public when it has a page.

text
app
ā”œā”€ā”€ page.tsx              → /
└── blog
    ā”œā”€ā”€ page.tsx          → /blog
    └── [slug]
        └── page.tsx      → /blog/hello
tsx
// 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.

text
app
ā”œā”€ā”€ page.tsx              → /
└── blog
    ā”œā”€ā”€ page.tsx          → /blog
    └── [slug]
        └── page.tsx      → /blog/hello
tsx
export default async function Page({ params, searchParams }) {
  const { id } = await params;
  const { q } = await searchParams;
}

searchParams are the query parameters.

url
https://example.com/blog/hello?q=search
tsx
export 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.

text
app
ā”œā”€ā”€ layout.tsx            # root layout (required)
└── dashboard
    ā”œā”€ā”€ layout.tsx        # wraps /dashboard/*
    └── page.tsx
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.

tsx
// 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.

text
app
└── dashboard
    ā”œā”€ā”€ loading.tsx       # shown while dashboard/page.tsx loads
    └── page.tsx
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.

text
app
ā”œā”€ā”€ global-error.tsx      # catches errors in the root layout
└── dashboard
    ā”œā”€ā”€ error.tsx         # catches errors in /dashboard/*
    └── page.tsx

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.

text
app
ā”œā”€ā”€ not-found.tsx         # root 404 page
└── blog
    ā”œā”€ā”€ not-found.tsx     # 404 for /blog/*
    └── [slug]
        └── page.tsx

tsx
import { notFound } from "next/navigation";
 
export default async function Post({ params }) {
  const post = await getPost((await params).id);
  if (!post) notFound(); // renders not-found.tsx
}
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.

text
app
ā”œā”€ā”€ template.tsx          # remounts on every navigation
└── page.tsx
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.

text
app
ā”œā”€ā”€ @modal
│   ā”œā”€ā”€ default.tsx       # fallback when no modal is active
│   └── login
│       └── page.tsx
└── layout.tsx            # receives { modal } as a prop
tsx
// 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.

text
app
└── api
    └── users
        └── route.ts      → /api/users
tsx
// 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.

text
project-root
ā”œā”€ā”€ middleware.ts         # root of the project, next to app/
└── app
    └── page.tsx
ts
// 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.

text
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

ts
// app/sitemap.ts
export default function sitemap() {
  return [{ url: "https://example.com", lastModified: new Date() }];
}
ts
// 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.

@thedevspaceio
www.thedevspace.io