Roadmap › Next.js — App Router, Folder Structure, Rendering and Full Stack with SQL Server › Module: Getting Started
Lessons in this course (8)
1. Next.js App Router folder structure — with sample files
Basic★ Must knowLesson 1 of 8
text
my-shop/
├── app/ ← App Router: folders = URL segments
│ ├── layout.tsx root layout (required): <html>, <body>, shared nav/footer
│ ├── page.tsx "/" home page
│ ├── globals.css global styles
│ ├── favicon.ico / icon.svg app icon (file convention)
│ ├── not-found.tsx 404 UI
│ ├── error.tsx error boundary for the segment (Client Component)
│ ├── loading.tsx instant loading UI (Suspense fallback)
│ ├── sitemap.ts · robots.ts · opengraph-image.tsx SEO file conventions
│ ├── products/
│ │ ├── page.tsx "/products"
│ │ ├── loading.tsx loading UI just for products
│ │ └── [id]/ dynamic segment
│ │ ├── page.tsx "/products/42"
│ │ └── edit/page.tsx "/products/42/edit"
│ ├── (shop)/ route GROUP — organises files, not part of the URL
│ │ ├── layout.tsx layout only for pages in this group
│ │ └── cart/page.tsx "/cart"
│ ├── admin/
│ │ ├── layout.tsx admin layout (sidebar)
│ │ └── [[...slug]]/page.tsx optional catch-all: "/admin", "/admin/a/b"
│ ├── api/
│ │ └── products/route.ts route handler: GET/POST "/api/products" (REST API)
│ └── _components/ private folder (underscore) — not a route
├── components/ shared UI components (Button.tsx, ProductCard.tsx)
├── lib/ server/client utilities: db.ts, auth.ts, validation.ts
├── public/ static files served as-is: /logo.png, /robots.txt (if static)
├── proxy.ts (middleware.ts before v16) runs before requests: auth redirects, rewrites, headers
├── next.config.ts Next.js configuration (images, redirects, headers)
├── .env.local secrets for local dev (never commit) — DB connection string
├── tsconfig.json · eslint.config.mjs · package.jsonSample files:
tsx
// app/layout.tsx — root layout
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = { title: { default: "My Shop", template: "%s · My Shop" }, description: "Buy great things" };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body><header>My Shop</header><main>{children}</main><footer>© 2026</footer></body>
</html>
);
}
// app/products/[id]/page.tsx — dynamic Server Component page
import { notFound } from "next/navigation";
import { getProduct } from "@/lib/products";
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
const p = await getProduct(Number((await params).id));
return { title: p?.name ?? "Product" };
}
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const product = await getProduct(Number((await params).id)); // runs on the server — can query the DB
if (!product) notFound();
return <h1>{product.name} — ₹{product.price}</h1>;
}
// app/products/loading.tsx
export default function Loading() { return <p>Loading products…</p>; }
// app/error.tsx — must be a Client Component
"use client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <div><p>Something went wrong.</p><button onClick={() => reset()}>Try again</button></div>;
}
// app/api/products/route.ts — REST endpoint
import { NextResponse } from "next/server";
import { listProducts, createProduct } from "@/lib/products";
export async function GET() { return NextResponse.json(await listProducts()); }
export async function POST(request: Request) {
const body = await request.json();
return NextResponse.json(await createProduct(body), { status: 201 });
}
// proxy.ts — protect /admin (named middleware.ts with export function middleware before Next 16)
import { NextResponse, type NextRequest } from "next/server";
export function proxy(request: NextRequest) {
if (!request.cookies.get("session")) return NextResponse.redirect(new URL("/login", request.url));
}
export const config = { matcher: ["/admin/:path*"] };Special files (in order of nesting): layout → template → error → loading → not-found → page. A folder becomes a public route only when it has page.tsx or route.ts.