Learn why Gartner just named Builder a Cool Vendor

Announcing Visual Copilot - Figma to production in half the time

Builder.io logo
Contact Sales
Contact Sales

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

Key Considerations for Next.js App Router Files

September 12, 2024

Written By Vishwas Gopinath

Next.js has become a powerhouse in the React ecosystem, and understanding its file conventions is key to getting the most out of it. In this blog post, we'll break down the 9 special routing files in the App Router, which replaces the older pages directory approach. We'll look at each file type and answer four main questions:

  1. What is it?
  2. Why should you care?
  3. How do you use it?
  4. What are the key considerations?

Let’s start with a brief intro about the App Router itself.

Next.js App Router

The Next.js App Router, introduced in Next.js 13, represents a shift from the previous pages directory approach. It uses folders in the app directory to define routes.

This new system supports layouts, loading states, and error handling at the route level. It also allows seamless mixing of client and server components, offering improved performance and a more flexible development experience from the first load onwards.

Now, let's dive into each of these special files, starting with the fundamental building block of Next.js routing.

page.tsx contains the UI components that are unique to a route.

This file is crucial because it works hand-in-hand with Next.js's folder structure to create your routing system. It means you can define the content for each page in your application.

  • Create a page.tsx file in any folder within your app directory.
  • The file's location corresponds directly to the URL path.
  • Export a default React component that defines the page content.
  • You can nest folders inside other folders to create nested routes.

Example:

// app/blog/page.tsx
export default function BlogPage() {
  return <h1>Welcome to my blog</h1>
}
  1. Export type: Always use export default for your page component, not a named export.
  2. Directory structure: Avoid mixing pages/ and app/ directories in the same project to prevent routing conflicts.
  3. Data fetching: Use the fetch API with async/await directly in your components.
  4. Component type: page.tsx components are Server Components by default. Add the 'use client' directive at the top of your file if you need client-side interactivity for client components.

layout.tsx creates a shared layout that wraps around your page content.

This way you can create reusable layouts, reducing redundant code and ensuring a cohesive user experience across your site. Layouts are also performance-optimized, as they don't re-render when navigating between pages.

  • Create a layout.tsx file in the app folder for a root layout, or in any subfolder for more specific layouts.
  • Export a default React component that accepts a children prop.

Example:

// app/layout.tsx
export default function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <header>Builder.io</header>
        <main>{children}</main>
        <footer>© 2024</footer>
      </body>
    </html>
  )
}
  1. Root layout: The root layout (app/layout.tsx) must define the <html> and <body> tags.
  2. Nesting: Be mindful of how nested layouts interact with each other.
  3. Route information: Layouts don't have access to the current route segment. Use useSelectedLayoutSegment or useSelectedLayoutSegments hooks if needed.
  4. Rendering behavior: Layouts are not re-rendered when navigating between pages. Avoid using route-specific data in layouts.

template.tsx creates a reusable template that wraps around your page content, similar to layout.tsx, but it re-renders on each navigation.

It's useful when you need a fresh state or want to trigger animations on every navigation, unlike layouts which persist across routes.

  • Create a template.tsx file in any folder within your app directory.
  • Export a default React component that accepts a children prop.

Example:

"use client";
import Link from "next/link";
import { motion } from "framer-motion";

export default function BlogTemplate({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <div>
      <ul>
        <li>
          <Link href="/blog/1">Post 1</Link>
        </li>
        <li>
          <Link href="/blog/2">Post 2</Link>
        </li>
      </ul>
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ duration: 0.5 }}
      >
        {children}
      </motion.div>
    </div>
  );
}
  1. Use cases: Use templates only when you specifically need their re-rendering behavior on each navigation.
  2. Performance: Be aware of potential performance impacts if overused, as they don't have the same optimizations as layouts.
  3. Layouts versus templates: Use templates in conjunction with layouts, not as an alternative.

loading.tsx creates a loading UI that's shown while the content of a route segment is being loaded.

It improves user experience by providing instant feedback during content loading, making your app feel more responsive and reducing perceived load times.

  • Create a loading.tsx file in the same directory as your page.tsx file.
  • Export a default React component that defines your loading UI.

Example:

// app/blog/loading.tsx
export default function Loading() {
  return <div>Loading blog posts...</div>
}
  1. Suspense boundary: The loading UI is automatically wrapped in a React Suspense boundary. Avoid nested Suspense usage in the loading component.
  2. Layout shift: Balance the benefit of showing a loading state against potential layout shifts when content loads.
  3. Scope: Loading states are shared by all pages in the same segment. Implement page-specific loading states differently if needed.
  4. Dynamic routes: Be aware that the loading state will show even when navigating between different dynamic routes.
  5. Hierarchy: loading.tsx only handles loading states for its route segment and children, not parent segments.

error.tsx creates a custom error UI that's displayed when an error occurs within a route segment.

You can gracefully handle runtime errors, providing a better user experience when things go wrong instead of crashing the entire app.

  • Create an error.tsx file in the same directory as your page.tsx file.
  • Export a default React component that accepts error and reset props.

Example:

'use client';
import { useRouter } from "next/navigation";
import { startTransition } from "react";

export default function Error({
    error,
    reset,
}: {
    error: Error & { digest?: string }
    reset: () => void
}) {
    const router = useRouter();
    
    function handleReset() {
        startTransition(() => {
            reset();
            router.refresh();
        });
    }
    
    return (
        <>
            <p>Error: {error.digest}</p>
            <p>
                <button onClick={handleReset}>Reset</button>
            </p>
        </>
    );
}
 1. Component type: error.tsx must be a Client Component (use the 'use client' directive).
2. Full Re-rendering: To re-render both client-side and server-side components:
  • Use startTransition() to wrap both reset() and router.refresh().
  • reset() alone re-renders only client-side components.
  • router.refresh() alone re-renders only server-side components.
  • Combining both within startTransition() ensures synchronized re-rendering of all components.
3. Error scope: It doesn't catch errors in the same segment's layout.tsx or template.tsx files.
4. Error bubbling: Errors bubble up to the nearest parent error boundary. Implement appropriate error boundaries at different levels of your app.

global-error.tsx creates a global error UI that catches and handles errors at the root of your Next.js application.

It ensures that your users always get a meaningful error message, even if something goes catastrophically wrong at the highest level of your app.

  • Create a global-error.tsx file in your app folder.
  • Export a default React component that accepts error and reset props.

Example:

'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  )
}
  1. Environment behavior: global-error.tsx only works in production. You'll get the default error overlay in development.
  2. Layout replacement: This component completely replaces the root layout when an error occurs.
  3. HTML structure: Include the and tags in your global-error.tsx component.
  4. Complexity: Keep the global-error.tsx component basic to minimize the risk of it causing errors itself.
  5. Error handling hierarchy: Use global-error.tsx as a last resort. Handle errors at more granular levels when possible.

not-found.tsx creates a custom UI for 404 Not Found errors in your Next.js application.

You can create a branded, helpful page that guides users back to valid content when they encounter pages that don't exist.

  • Create a not-found.tsx file in your app folder or in specific subfolders.
  • Export a default React component that defines your 404 page content.

Example:

import Link from 'next/link'

export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href="/">Return Home</Link>
    </div>
  )
}
  1. Scope: not-found.tsx only handles not-found scenarios for its folder and subfolders.
  2. notFound() function: When using the notFound() function to trigger a not-found state, ensure a not-found.tsx file exists in the same segment or a parent segment.
  3. API routes: not-found.tsx doesn't handle 404 errors for API routes. Handle those separately.

default.tsx provides a fallback UI for parallel routes when no specific match is found.

It's crucial for creating smooth user experiences in complex routing scenarios, especially when using parallel routes.

  • Create a default.tsx file in a folder that's prefixed with @, which denotes a slot for parallel routing.
  • Export a default React component that defines your fallback UI.

Example:

// app/@sidebar/default.tsx
export default function DefaultSidebar() {
  return (
    <div>
      <h2>Default Sidebar Content</h2>
      <p>Select a category to see more information.</p>
    </div>
  )
}
  1. Usage context: default.tsx is only relevant in the context of parallel routing.
  2. Compatibility: Ensure your default.tsx component is compatible with all possible states of your parallel routes.
  3. Rendering behavior: default.tsx will be rendered initially and then replaced when a more specific route is matched.
  4. Design: Design your default.tsx content to provide meaningful information or functionality in the absence of specific route content.

Use route.ts to create API routes directly within your app folder.

It enables you to create serverless API endpoints alongside your frontend code, giving you fine-grained control over how your application responds to HTTP requests.

  • Create a route.ts file in any route segment of your app directory.
  • Export functions named after HTTP methods, such as GET, POST, PUT, DELETE.

Example:

import { NextResponse } from "next/server";

// Dummy data store (in a real app, this would be a database)
let todos = [
  { id: 1, title: "Learn Next.js", completed: false },
  { id: 2, title: "Build an app", completed: false },
];

export async function GET() {
  return NextResponse.json(todos);
}

export async function POST(request: Request) {
  const data = await request.json();
  const newTodo = {
    id: todos.length + 1,
    title: data.title,
    completed: false,
  };
  todos.push(newTodo);
  return NextResponse.json(newTodo, { status: 201 });
}
  1. File conflicts: Don't place a page.tsx file in the same folder as your route.ts file with a GET handler to avoid conflicts.
  2. Execution context: Remember that route.ts files are always executed on the server. Don't use browser-specific APIs here.
  3. Static generation: API routes defined with route.ts are not statically generated at build time.

While the Pages Router is still supported, the App Router takes priority in new projects.

So there you have it — the 9 special files that make routing in Next.js’ App Router tick. The App Router introduces many new features that make it easier to build interactive and performant applications.

With its file system hierarchy and React Server Components, it offers a powerful way to structure your projects. It might seem like a lot at first, but each file has its purpose and can make your life easier once you get the hang of it.

Remember, you don't need to use all of these in every project. Start with the basics like page.tsx and layout.tsx, and gradually incorporate the others as you need them. Before you know it, you'll be building complex, efficient and production ready Next.js apps like a pro.

Share

Twitter
LinkedIn
Facebook
Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot
Newsletter

Like our content?

Join Our Newsletter

Continue Reading
AI8 MIN
Generate Figma Designs with AI
November 25, 2024
Design to code5 MIN
Builder.io Named a Cool Vendor in the 2024 Gartner® Cool Vendors™ in Software Engineering: User Experience
November 21, 2024
AI8 MIN
How to Build Reliable AI Tools
November 15, 2024