Practice: Full-stack customer applications with React and Next.js

Generated by Claude through GigAI 0.1.11.10 on 2026-10-11.

Main tool: Next.js · Next.js puts routing, server rendering, server-side data access, auth checks and end-to-end testing hooks in one codebase, so every subtopic of this lesson can be practised in a single project on a laptop. · Alternatives: React Router in framework mode (formerly Remix) is built around web-standard loaders and actions with less caching machinery; a Vite single-page React app is simpler but has no server rendering or server-side data access and needs a separate backend; SvelteKit gives the same full-stack shape with a different component model; Astro suits content-heavy sites with small interactive islands rather than app-like screens.

Every step links into the tool's own documentation; links were checked on 2026-10-11 · version 1

Setup (one time)

  • 1. Install a current Node.js LTS release, then create a new Next.js project with create-next-app using the Playwright example, and start the dev server.

    One scaffold gives you the App Router, TypeScript and a working test runner, so later steps add to it instead of rewiring it.

    Explained in: Testing: Playwright | Next.js (nextjs.org), checked 2026-10-11

    Done when: The dev server prints a local address and the starter page loads in your browser.

  • 2. Install Tailwind CSS and its peer dependencies into the project.

    Customer screens are judged on polish, and utility classes are the fastest way to style components without leaving the component file.

    Explained in: Guides: Tailwind CSS v3 | Next.js (nextjs.org), checked 2026-10-11

    Done when: The Tailwind packages appear in the project dependencies and the config files exist.

  • 3. Configure the Tailwind content paths and add the Tailwind directives to the global stylesheet imported by the root layout.

    Tailwind only generates classes for files it is told to scan, so a wrong path gives an unstyled page with no error.

    Explained in: Guides: Tailwind CSS v3 | Next.js (nextjs.org), checked 2026-10-11

    Done when: A utility class added to the home page visibly changes its styling after a refresh.

Steps

  • 1. Write an async Server Component page that fetches a list from a public JSON API with fetch and renders it.

    This is the default way data reaches the screen in the App Router: the component awaits data on the server and ships HTML. It is the first run that everything else builds on.

    Explained in: Getting Started: Fetching Data | Next.js (nextjs.org), checked 2026-10-11

    Done when: The list renders in the browser and the browser network tab shows no request to the JSON API.

  • 2. Create a Client Component with the use client directive that takes props and holds state, such as a filter box or a like button, and place it on the page.

    Components, props and state are the React core model, and in Next.js state and event handlers only work in components marked as client.

    Explained in: Getting Started: Server and Client Components | Next.js (nextjs.org), checked 2026-10-11

    Done when: Clicking or typing in the component updates the screen without a page reload.

  • 3. Write the page so the Server Component fetches the data and passes it as props to the Client Component, then try passing a non-serializable value to see the failure.

    The server and client boundary is where most App Router bugs come from. Seeing what can and cannot cross it makes the rule concrete.

    Explained in: Getting Started: Server and Client Components | Next.js (nextjs.org), checked 2026-10-11

    Done when: The Client Component renders server-fetched data, and you have seen the error raised for a value that cannot cross the boundary.

  • 4. Start PostgreSQL in a local container, then load rows from it inside a Server Component through an ORM or database client.

    Customer applications read from the customer's own data, and querying on the server keeps credentials and query logic out of the browser bundle.

    Explained in: Getting Started: Fetching Data | Next.js (nextjs.org), checked 2026-10-11

    Done when: Rows you inserted into the local database appear on the page, and the connection string is absent from the browser sources.

  • 5. Create a deliberately slow data component, wrap it in a Suspense boundary with a skeleton fallback, and add a loading file for the route.

    AI-backed screens often wait on slow calls. Streaming lets the page shell show at once while the slow part arrives later.

    Explained in: Getting Started: Fetching Data | Next.js (nextjs.org), checked 2026-10-11

    Done when: The page shell and skeleton appear immediately and the slow section fills in a few seconds later without a full reload.

  • 6. Compare the client fetching patterns in the guide against your server-fetched page and write down which one each of your screens needs.

    Not every screen should fetch on the server: polling, live updates and user-driven refetching belong on the client. Choosing on purpose avoids duplicated fetch logic.

    Explained in: Guides: Client-side data fetching | Next.js (nextjs.org), checked 2026-10-11

    Done when: You have a short note that maps each screen in your app to one pattern with a one-line reason.

  • 7. Configure the TanStack Query provider, write a form that mutates a database row through a Server Action, and coordinate the server and client caches after the mutation.

    A mutation that saves but leaves stale data on screen is the most visible bug in a customer demo. This is where you learn to keep both caches in agreement.

    Explained in: Client-side data fetching: TanStack Query | Next.js (nextjs.org), checked 2026-10-11

    Done when: Submitting the form changes the row in PostgreSQL and the list on screen shows the new value without a manual refresh.

  • 8. Write sign-up and login forms backed by a stateless session: generate a secret key, encrypt the session, and set it in a cookie with the recommended options.

    Building the session by hand once shows what an auth library such as Auth.js does for you, and what to check when a customer security review asks.

    Explained in: Guides: Authentication | Next.js (nextjs.org), checked 2026-10-11

    Done when: After login the browser holds an encrypted HTTP-only session cookie, and after logout it is gone.

  • 9. Create a data access layer that verifies the session before every query, and call it from your pages, Server Actions and route handlers.

    Authorization must sit next to the data, not only in a redirect, because Server Actions and route handlers can be reached directly. One layer keeps the check consistent.

    Explained in: Guides: Authentication | Next.js (nextjs.org), checked 2026-10-11

    Done when: A signed-out request to a protected page or action is redirected or rejected, and a signed-in user sees only their own rows.

  • 10. Write a Playwright end-to-end test that logs in, submits the form and checks the updated list, then run it with the CI settings the guide describes.

    UI tests are what let you change a customer screen the day before a demo with confidence. Running them the way CI does makes the result reproducible.

    Explained in: Testing: Playwright | Next.js (nextjs.org), checked 2026-10-11

    Done when: The test suite passes in headless mode from a clean checkout with a single command.

  • 11. Configure a deployment ID in the Next config, build the app twice with different IDs, and run the two builds side by side on different local ports as a stand-in for a preview and a live deployment.

    Preview and rolling deployments mean two versions are live at once. The deployment ID is how Next.js keeps a browser from mixing assets of different builds. A hosted preview platform is not needed to see this.

    Explained in: next.config.js: deploymentId | Next.js (nextjs.org), checked 2026-10-11

    Done when: Both builds serve on their own port, and you can find the differing deployment ID in the requests each one makes.

  • 12. Read the reference for exposing the testing API in a production build, enable it for a test-only build, then build and start the app in production mode against local PostgreSQL and run the Playwright suite against it.

    The dev server hides caching, minification and prerendering behaviour, so the final check must run against the same kind of build a customer receives.

    Explained in: next.config.js: exposeTestingApiInProductionBuild | Next.js (nextjs.org), checked 2026-10-11

    Done when: The full end-to-end suite passes against the production build served by next start, with the option switched off again for the real release build.