Skip to content
Tutorial··4 min read

Build a full-stack Next.js app with Claude Code — complete walkthrough

Real step-by-step: use Claude Code to scaffold, build, and deploy a Next.js 14 App Router app with Supabase auth, a protected dashboard, and a public landing page.

By

Build a full-stack Next.js app with Claude Code — complete walkthrough

This is a real walkthrough, not a demo. We're building a working Next.js 14 app — public landing page, GitHub OAuth sign-in, protected dashboard — using Claude Code as the primary coding agent. Every command is real. Every instruction to the agent is exact.

What we're building

  • Public landing page (marketing site)
  • GitHub OAuth sign-in via Supabase Auth
  • Protected /dashboard route
  • A simple "saved links" feature (CRUD, stored in Supabase)
  • Deploy to Vercel

Prerequisites

  • Node.js 18.17 or later (required by Next.js 14)
  • Claude Code installed (on macOS or Linux: curl -fsSL https://claude.ai/install.sh | bash)
  • A Supabase account (free tier works)
  • A Vercel account (free tier works)
  • Git + GitHub account

Step 1 — Scaffold the project

This walkthrough targets Next.js 14, so pin that version:

bash
npx create-next-app@14 my-app --typescript --tailwind --app --eslint
cd my-app

Before starting Claude Code, install the right skill into the project:

bash
curl -fsSL --create-dirs -o .claude/skills/nextjs-app-router-pro/SKILL.md https://claudeskil.com/skills/nextjs-app-router-pro/SKILL.md

This gives Claude Code ready-made context about Next.js 14 patterns — App Router, Server Components, Server Actions, streaming, and ISR — which it loads whenever a request matches. You won't need to explain these every session. Then start Claude Code:

bash
claude

Now set up Supabase:

install @supabase/supabase-js and @supabase/ssr. Create lib/supabase/client.ts (browser client) and lib/supabase/server.ts (server client using cookies from next/headers). Use the environment variables NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY.

Review what it produces. The server client should use createServerClient from @supabase/ssr with a cookies() adapter. If it uses the older createClient with cookies manually, ask it to update to the ssr pattern.

Step 2 — Auth middleware

add a middleware.ts in the project root that uses Supabase to refresh the session on every request. Protect all /dashboard/* routes — redirect unauthenticated users to /sign-in. Public routes: /, /sign-in, /sign-up, /api/auth/callback.

Claude Code will write the middleware, including the matcher in the config object exported from middleware.ts. Read the matcher pattern carefully — a wrong regex here means your /api routes get auth-checked, which breaks everything.

Step 3 — Sign-in page

create app/sign-in/page.tsx. It should have a "Sign in with GitHub" button that calls supabase.auth.signInWithOAuth with provider: 'github' and redirectTo pointing to /api/auth/callback. Show a spinner while the redirect is happening. Style it with Tailwind — clean, centered, dark background.

Then create the OAuth callback:

create app/api/auth/callback/route.ts. It handles the code exchange for a Supabase session using exchangeCodeForSession, then redirects to /dashboard.

Step 4 — Protected dashboard

create app/dashboard/layout.tsx. It should get the current user from Supabase on the server using the server client. If no user, redirect to /sign-in. Show a simple sidebar with: Dashboard, Saved Links, Settings. Add a sign-out button that calls supabase.auth.signOut via a Server Action and redirects to /.

create app/dashboard/page.tsx. Show "Welcome back, [user.email]" and a count of the user's saved links (query the links table, count only).

First, create the database table. Go to your Supabase dashboard > SQL Editor and run:

sql
create table links (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users not null,
  url text not null,
  title text,
  created_at timestamptz default now()
);
alter table links enable row level security;
create policy "users can only see their own links"
  on links for all
  using (auth.uid() = user_id);

Then let Claude Code build the UI:

create app/dashboard/links/page.tsx. Fetch the current user's links from Supabase (server component). Render them in a list. Add a form at the top with a URL input and a title input. The form submission should be a Server Action that inserts a new row into the links table with the current user's id, then revalidatePath('/dashboard/links').

Step 6 — Landing page

create app/page.tsx — a marketing landing page. Hero section with headline "Save links that matter" and a "Get started free" CTA that links to /sign-in. Three feature sections (Clean UI, Private by default, Works everywhere). A simple footer. Tailwind dark theme.

At this point, run the dev server:

bash
npm run dev

Test the full flow manually: landing → sign in → dashboard → add a link → see it appear.

Step 7 — Fix the inevitable issues

Something will be wrong. Common ones:

If the OAuth redirect goes to localhost in production: set the NEXT_PUBLIC_APP_URL env var and update the redirectTo to use it.

If the Server Action isn't revalidating: make sure revalidatePath is imported from next/cache, not next/navigation.

If the middleware redirects are looping: check that your matcher doesn't include /api/auth/callback — that route must be public or the session exchange never completes.

Ask Claude Code to fix each issue:

the OAuth redirect is using localhost:3000 even in production. fix it to use NEXT_PUBLIC_APP_URL with a fallback to localhost:3000.

Step 8 — Deploy to Vercel

bash
npx vercel --prod

Set the environment variables in the Vercel dashboard:

text
NEXT_PUBLIC_SUPABASE_URL = from your Supabase project settings
NEXT_PUBLIC_SUPABASE_ANON_KEY = from your Supabase project settings
NEXT_PUBLIC_APP_URL = https://your-app.vercel.app

In your Supabase Auth settings, add the Vercel URL to the allowed redirect URLs.

Test the full flow on production.

What you've learned

By doing this walkthrough you've experienced:

  • How to write precise, effective instructions for Claude Code
  • Where the agent is reliable (boilerplate, patterns, CRUD) vs. where you need to supervise (auth flows, env var wiring)
  • How skills (nextjs-app-router-pro) give the agent domain context
  • The review habit: read every diff, especially on auth-related code

The source code for this walkthrough is the kind of thing you could turn into a SKILL.md yourself — "Full-stack Next.js with Supabase auth" as a reusable pattern. That's exactly how the skills marketplace grows.

Explore more Next.js skills at claudeskil.com/category/fullstack.