Neural Tech Daily
dev-tutorials

Next.js App Router in 60 minutes: build a global-ready dashboard

Step-by-step tutorial: scaffold a Next.js 16 App Router project, build a dashboard with layouts, dynamic routes, server components, in 60 min.

Updated ~13 min read
Share

The bottom line

A working Next.js App Router dashboard in 60 minutes is achievable for a developer with Node.js 20.9 or newer installed, a terminal they’re comfortable in, and basic React familiarity 1 . This tutorial scaffolds a Next.js 16.2 project with create-next-app, walks through the App Router file conventions (layout.tsx, page.tsx, [id] dynamic segments), wires a sidebar shell with two nested routes, fetches data in a Server Component, and adds a dynamic detail route. Every command and code block is verifiable against the official Next.js docs at nextjs.org/docs/app. This is not a Vercel-deploy tutorial; the dashboard runs locally on http://localhost:3000 and the build script is wired so a production deploy is a configuration step, not a code rewrite.

Next.js 16.2 ships with Turbopack as the default bundler, the App Router as the default routing system, and AGENTS.md scaffolding for AI coding agents per the 18 March 2026 release announcement 4 . The Pages Router still exists but is not the path this tutorial covers.

Authentication, database integration, and Vercel deploy configuration are separate articles.

What you’ll build

The end state is a dashboard application with a persistent sidebar layout, a home overview page, a “users” list page that fetches data on the server, and a per-user detail page reached by a dynamic [id] segment. Five route files at the end:

  • app/layout.tsx: the root layout with <html> and <body>.
  • app/page.tsx: the dashboard home (overview).
  • app/(dashboard)/layout.tsx: the sidebar shell layout, scoped via a route group.
  • app/(dashboard)/users/page.tsx: the users list page (Server Component, fetches data).
  • app/(dashboard)/users/[id]/page.tsx: the per-user detail page (dynamic segment).

Plus a <Link> for client-side navigation and next.config.ts for any environment configuration the reader wants to add later.

Prerequisites

Before starting, the reader needs:

  • Node.js 20.9 or newer. Confirm with node --version. The official Next.js installation docs state 20.9 as the minimum 1 . Node.js 22 LTS is a safer floor for greenfield projects in 2026.
  • A package manager. pnpm, npm, yarn, or bun all work; this tutorial uses pnpm because the official docs list it as the first option 1 .
  • A terminal. Terminal.app or iTerm on macOS, any Linux terminal, PowerShell or WSL on Windows.
  • A code editor. VS Code, Cursor, or any editor with TypeScript and JSX support. The official docs recommend installing the Next.js VS Code TypeScript plugin for inline type checks 1 .
  • Basic React familiarity. A reader who has rendered JSX and used useState in a previous project will keep up. A reader who has never written React should follow the React tutorial first.

A modern browser at versions Next.js officially supports is also required: Chrome 111+, Edge 111+, Firefox 111+, or Safari 16.4+ 1 . Any current 2026 browser meets this floor.

The Next.js installation documentation page on nextjs.org, version 16.2.6, showing the create-next-app CLI command and the recommended defaults this tutorial accepts

Image: Next.js official Installation docs (nextjs.org/docs/app/getting-started/installation), used for editorial coverage of the create-next-app workflow this tutorial follows.

The mental model

Next.js App Router uses file-system routing inside an app/ directory. Folders define route segments that map to URL segments; specific filenames inside a folder define UI for that segment. The four file conventions that carry most of the weight for a first dashboard 2 :

  • page.tsx in a folder makes that route publicly accessible. app/page.tsx is /, app/users/page.tsx is /users.
  • layout.tsx in a folder wraps all page.tsx files in that folder and its descendants. Layouts preserve state across navigation; they do not re-render when a child page changes.
  • [id] folder syntax wraps a segment name in square brackets to make it a dynamic route segment. app/users/[id]/page.tsx matches /users/123, /users/anything.
  • (name) folder syntax wraps a segment in parentheses to create a route group: the folder organises files without affecting the URL. app/(dashboard)/users still resolves to /users.

The root app/layout.tsx is required and must contain <html> and <body> tags 2 . Next.js will scaffold this for the reader automatically; the rest is on the developer.

Server Components are the default in the App Router. Any component without the 'use client' directive at the top runs on the server, can await data directly in the component function, and ships zero JavaScript to the browser for the rendering of its own markup. Client Components opt in with 'use client' at the top of the file and run in the browser; they handle interactivity (onClick, useState, event handlers).

Step 1: Scaffold the project

In a terminal, in the directory where the project should live:

pnpm create next-app@latest nextjs-dashboard-demo --yes

The --yes flag accepts the recommended defaults: TypeScript, ESLint, Tailwind CSS, App Router, Turbopack, import alias @/*, and an AGENTS.md file 1 . For npm users:

npx create-next-app@latest nextjs-dashboard-demo --yes

create-next-app installs around 200 MB of dependencies (the first run; subsequent installs are cached via pnpm’s content-addressable store). It finishes in 30–90 seconds depending on network speed.

cd into the new project and start the dev server:

cd nextjs-dashboard-demo
pnpm dev

The terminal prints Local: http://localhost:3000. Open that URL; the default “Get started by editing app/page.tsx” splash screen renders.

Step 2: The root layout

create-next-app already wrote app/layout.tsx. Open it. The relevant skeleton matches what the official docs prescribe 2 :

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

This is the only layout required at the root. Replace the scaffolded app/page.tsx content with a minimal placeholder:

export default function Page() {
  return (
    <main style={{ padding: 24 }}>
      <h1>Dashboard home</h1>
      <p>An overview of activity across the application.</p>
    </main>
  );
}

Save. The browser refreshes automatically; “Dashboard home” appears.

Step 3: A route group for the dashboard shell

Route groups let multiple routes share a layout without leaking a URL segment. Create a (dashboard) folder and move the dashboard shell inside it:

mkdir -p "app/(dashboard)"

Create app/(dashboard)/layout.tsx:

import Link from 'next/link';

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr', minHeight: '100vh' }}>
      <aside style={{ padding: 24, borderRight: '1px solid #e5e7eb' }}>
        <h2 style={{ marginTop: 0 }}>Dashboard</h2>
        <nav style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <Link href="/">Home</Link>
          <Link href="/users">Users</Link>
        </nav>
      </aside>
      <section style={{ padding: 24 }}>{children}</section>
    </div>
  );
}

The <Link> import from next/link enables client-side navigation with prefetching, which is the App Router’s primary navigation pattern 7 . The inline styles keep this tutorial framework-agnostic; Tailwind utility classes would also work since create-next-app installed Tailwind by default 1 .

Move app/page.tsx into the route group so it inherits the sidebar:

mv app/page.tsx "app/(dashboard)/page.tsx"

Save and reload. The sidebar appears at /; the route URL is unchanged because the (dashboard) segment is wrapped in parentheses and so does not contribute to the URL 2 .

The Next.js Layouts and Pages documentation page on nextjs.org documenting the page.tsx, layout.tsx, and route group file conventions this tutorial uses

Image: Next.js official Layouts and Pages docs (nextjs.org/docs/app/getting-started/layouts-and-pages), used for editorial coverage of the App Router file conventions this tutorial follows.

Step 4: A users list route with server-side data fetching

Create the users folder and a page.tsx inside it:

mkdir -p "app/(dashboard)/users"

For this tutorial, use a public placeholder API rather than connecting a real database. Create a small data layer at app/(dashboard)/users/data.ts:

export type User = {
  id: number;
  name: string;
  username: string;
  email: string;
};

export async function getUsers(): Promise<User[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/users', {
    next: { revalidate: 60 },
  });
  if (!res.ok) {
    throw new Error(`Failed to fetch users: ${res.status}`);
  }
  return res.json();
}

export async function getUser(id: string): Promise<User | null> {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
    next: { revalidate: 60 },
  });
  if (!res.ok) return null;
  return res.json();
}

The next: { revalidate: 60 } option is the App Router’s data-cache hint: responses are cached and re-validated at most once every 60 seconds. JSONPlaceholder is a free read-only API; in a real dashboard, this would be a database call, an authenticated REST endpoint, or a typed RPC client.

Now app/(dashboard)/users/page.tsx:

import Link from 'next/link';
import { getUsers } from './data';

export default async function UsersPage() {
  const users = await getUsers();
  return (
    <div>
      <h1 style={{ marginTop: 0 }}>Users</h1>
      <ul style={{ paddingLeft: 16 }}>
        {users.map((user) => (
          <li key={user.id} style={{ marginBottom: 8 }}>
            <Link href={`/users/${user.id}`}>
              {user.name} <span style={{ color: '#6b7280' }}>(@{user.username})</span>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Two things to notice. First, the component is an async function — Server Components can await data directly in the function body without a useEffect wrapper or a separate API route 2 . Second, the <Link> href interpolates user.id to build a path like /users/3; that path needs a dynamic segment to resolve.

Reload /users in the browser. A list of names renders, each linked to a per-user URL that currently 404s. The dynamic segment is the next step.

Step 5: A dynamic detail route

Create the dynamic segment:

mkdir -p "app/(dashboard)/users/[id]"

Create app/(dashboard)/users/[id]/page.tsx:

import Link from 'next/link';
import { notFound } from 'next/navigation';
import { getUser } from '../data';

export default async function UserDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const user = await getUser(id);
  if (!user) notFound();

  return (
    <div>
      <Link href="/users">← Back to users</Link>
      <h1 style={{ marginTop: 16 }}>{user.name}</h1>
      <dl style={{ display: 'grid', gridTemplateColumns: '140px 1fr', rowGap: 8 }}>
        <dt>Username</dt>
        <dd>@{user.username}</dd>
        <dt>Email</dt>
        <dd>{user.email}</dd>
        <dt>User ID</dt>
        <dd>{user.id}</dd>
      </dl>
    </div>
  );
}

Two App Router conventions are at work here. params is a Promise that resolves to the dynamic-segment values, per the official Dynamic Routes API reference 6 . notFound() from next/navigation short-circuits the request and renders the nearest not-found.tsx (or the default 404 if none exists). The bracket-folder syntax [id] is what the official Layouts and Pages docs prescribe for a per-resource detail page 2 .

Reload /users, click any user. The detail page renders with the user’s data. Click “Back to users” to return.

Step 6: Navigation polish

The dashboard works, but the sidebar links don’t highlight the active route. Convert the sidebar to a Client Component so it can read the current pathname.

Replace app/(dashboard)/layout.tsx with two files. First, keep app/(dashboard)/layout.tsx as a Server Component that imports a client sidebar:

import { Sidebar } from './Sidebar';

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr', minHeight: '100vh' }}>
      <Sidebar />
      <section style={{ padding: 24 }}>{children}</section>
    </div>
  );
}

Then create app/(dashboard)/Sidebar.tsx:

'use client';

import Link from 'next/link';
import { usePathname } from 'next/navigation';

const links = [
  { href: '/', label: 'Home' },
  { href: '/users', label: 'Users' },
];

export function Sidebar() {
  const pathname = usePathname();
  return (
    <aside style={{ padding: 24, borderRight: '1px solid #e5e7eb' }}>
      <h2 style={{ marginTop: 0 }}>Dashboard</h2>
      <nav style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {links.map((link) => {
          const active =
            link.href === '/' ? pathname === '/' : pathname.startsWith(link.href);
          return (
            <Link
              key={link.href}
              href={link.href}
              style={{ fontWeight: active ? 700 : 400 }}
            >
              {link.label}
            </Link>
          );
        })}
      </nav>
    </aside>
  );
}

The 'use client' directive at the top of Sidebar.tsx opts the component into client-side rendering, which is what usePathname requires. The parent layout.tsx stays as a Server Component, so the sidebar shell still benefits from Server Component rendering and only the sidebar’s interactivity ships to the browser.

Step 7: Production build

Stop the dev server. Run a production build to confirm the routes type-check and the build pipeline succeeds:

pnpm build

next build runs Turbopack (the default bundler since Next.js 16) and produces an optimised .next/ output 1 . Build output lists each route under Route (app) with its rendering strategy: for static, ƒ for dynamic. The users list will be dynamic because revalidate: 60 plus fetch makes it data-driven; the home page will be static.

To serve the production build:

pnpm start

The dashboard now serves from http://localhost:3000 using the production bundle.

Globally-ready notes

A handful of choices in this tutorial map onto Next.js’s globally-deployable defaults:

  • next/image. This tutorial uses inline styles for layout to stay framework-agnostic; production dashboards should use next/image for hero / avatar images so Next.js handles responsive sizing, lazy loading, and AVIF/WebP conversion 1 .
  • <Link> everywhere for in-app navigation. Prefetching, client-side transitions, and bundle-splitting all hinge on using <Link> (not a raw <a> tag) for navigation between routes inside the app 7 .
  • revalidate is the App Router’s caching dial. A dashboard whose data changes every few seconds wants revalidate: 0 (no cache) or a tagged revalidation; a marketing dashboard whose data changes daily wants revalidate: 86400. Pick per route, not per app.
  • Environment variables. .env.local for development, .env.production for production builds; never commit either. Variables prefixed with NEXT_PUBLIC_ are exposed to the browser; everything else stays server-only.

Things that commonly go wrong

A short list of issues that bite first-time App Router users:

  • Module not found: Can't resolve '@/...': the import alias hasn’t been set up. Check tsconfig.json includes "baseUrl": "." and "paths": {"@/*": ["./*"]}. create-next-app with --yes does this automatically 1 .
  • params is a Promise; await it: Next.js 16’s params is a Promise, not a plain object. The detail-page example above shows the correct await params pattern 6 .
  • use client directive missing for hooks: any component using useState, useEffect, usePathname, or DOM event handlers must start with 'use client'. The error surface in Next.js 16 is explicit but easy to miss if scrolling fast.
  • The layout doesn’t appear: a route group like (dashboard) only applies its layout to files INSIDE that group. If a page lives in app/page.tsx (outside the group), the group’s layout will not wrap it.
  • next build fails with a TypeScript error: next dev is permissive about types; next build is strict. Run pnpm tsc --noEmit locally to catch type errors faster than waiting for the full build.

What was deliberately skipped

This tutorial covers the smallest meaningful App Router loop. A production dashboard would also wire authentication (NextAuth.js, Clerk, or session-cookie auth via Server Actions), a real database (Postgres via Drizzle / Prisma, MongoDB, or a managed Supabase / PlanetScale instance), error boundaries via error.tsx, loading states via loading.tsx and React Suspense, and middleware for redirects / locale routing. Each adds one or two files; none change the App Router file-convention model this tutorial covers.

Parallel routes (@analytics named slots), intercepting routes ((.)photo), Server Actions for form submissions, and edge runtime considerations are separate articles.

The Next.js documentation root page on nextjs.org displaying the version 16.2.6 indicator that this tutorial's commands and APIs are verified against

Image: Next.js official documentation root (nextjs.org/docs), used for editorial coverage of the version indicator (16.2.6) this tutorial’s APIs are verified against.

Recap

A working Next.js 16 App Router dashboard in five route files plus one Client Component sidebar. The mental model is small: folders are route segments, page.tsx makes a route public, layout.tsx wraps descendants, [id] makes a segment dynamic, and (name) groups routes without affecting the URL. Server Components are the default; opt into Client Components only where interactivity needs them. The official Next.js installation docs 1 and the Layouts and Pages reference 2 are the canonical companions to this tutorial; the version banner reads 16.2.6 at writer-time 3 .

The vercel/next.js GitHub repository page on github.com documenting the Next.js source code, release cycle, and contributor model this tutorial's framework is built on

Image: vercel/next.js GitHub repository (github.com/vercel/next.js), used for editorial coverage of the Next.js project this tutorial covers.

How this article was made: an autonomous AI pipeline researched, drafted, fact-checked, and reviewed this piece, aggregating publicly-available information from the sources consulted below. AI (artificial intelligence) can make mistakes, so please cross-check the consulted sources before acting on anything here. Neural Tech Daily is not liable for decisions or outcomes based on this article.

Sources consulted

Cited Sources

  1. 1. Next.js — official Installation docs (version 16.2.6 indicator, minimum Node.js 20.9, create-next-app --yes defaults to TypeScript + ESLint + Tailwind + App Router + Turbopack, supported browser floors Chrome 111 / Edge 111 / Firefox 111 / Safari 16.4) (accessed )
  2. 2. Next.js — official Layouts and Pages docs (file-system routing, page.tsx and layout.tsx conventions, root layout required with html and body tags, [slug] dynamic segments, route groups via parentheses) (accessed )
  3. 3. Next.js — documentation root (version 16.2.6 indicator at writer-time) (accessed )
  4. 4. Next.js — Blog (Next.js 16.2 release announcement, 18 March 2026; Turbopack default bundler; AGENTS.md scaffolding in create-next-app) (accessed )
  5. 5. vercel/next.js — official GitHub repository (Next.js source code, release cycle, release-notes archive) (accessed )
  6. 6. Next.js — Dynamic Routes API reference ([slug] folder syntax, params as a Promise in Next.js 16, generateStaticParams for build-time generation) (accessed )
  7. 7. Next.js — Link component API reference (next/link, prefetching behaviour, client-side transitions as the App Router's primary navigation pattern) (accessed )

Anonymous · no cookies set

Report a problem with this article

Articles are produced by an autonomous AI pipeline; mistakes do happen. Tell us what's wrong and the editorial review will revisit the claim.

Category

Found this useful? Share it.