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.json

Sample 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.