# Architecture

URL: https://ship-and-scale.vercel.app/docs/build/architecture

How requests, permissions and services fit together, with the commands you'll use while building features.

These notes help you and your agent extend the starter without breaking workspace permissions or exposing secrets. Read them when you start building features; you don't need them for Setup or Kickstart. For what each tool does, start with [Tech stack](/docs/build/codebase).

## One application, hosted services [#one-application-hosted-services]

Your app is one Next.js App Router application. Pages, server actions, HTTP routes and reusable UI live in the same repository. Hosted Supabase owns Postgres, Auth, Storage and Edge Functions. Stripe owns checkout and billing objects, Resend sends email, and AI requests go through OpenRouter from the server.

Local development runs Next.js on your computer against those hosted services, with no local database or Docker container. The Ship & Scale manual you're reading is separate from the product documentation included in your app.

## Next.js, React and TypeScript [#nextjs-react-and-typescript]

Next.js supplies the routing and server runtime. React components describe the interface, while TypeScript catches mismatched data shapes before a production build. App Router folders determine routes: `app/dashboard` owns protected product pages, and `app/api` owns HTTP endpoints.

Server Components can load authorized data before rendering. Add a Client Component for interaction such as an editable form or a menu. A server action handles a submitted mutation, validates its input and checks access. You can follow a complete example in [Build your first workspace feature](/docs/build/first-feature).

## Server and browser boundaries [#server-and-browser-boundaries]

Client Components use `'use client'` when they need state, events or browser APIs. Server actions use `'use server'` and still need authentication, validation and authorization: a form is an HTTP entry point, even if the UI hides it.

Keep modules importing `server-only` out of client imports. Only intentionally public values use `NEXT_PUBLIC_`. Pass the minimum presentation data into client props; never pass a Supabase admin client, API secret or provider response containing private account details.

## Tailwind CSS and the component library [#tailwind-css-and-the-component-library]

Tailwind provides utility classes; shadcn/ui places editable component source in `components/ui/`. The installed preset uses Base UI's interaction primitives with the Vega style, neutral colors, Inter typography and Lucide icons. All 61 component files are in your repository, so you can inspect and adapt their actual props.

Shared design tokens in `app/globals.css` control colors, borders and spacing. Reusing those tokens and the existing buttons, dialogs and fields keeps a new feature consistent with the rest of the product. See [Branding and UI customization](/docs/build/customization) before replacing the component foundation.

## Hosted Supabase [#hosted-supabase]

The browser and server use the Supabase client libraries with the current user's session. Row-level security, or RLS, checks which rows that user can access; workspace membership and roles determine the permitted reads and writes.

Schema and permission changes live in versioned SQL files under `supabase/migrations/`. Kickstart applies them through the Supabase Management API and records a checksum for each file. The default workflow has no ORM schema generation, local database container or direct database password. [Database and migrations](/docs/core/database) explains how to extend it without bypassing that record.

Auth handles sessions, recovery and provider sign-in. Storage holds private avatars; workspace logos use access-controlled database rows. Edge Functions run the hosted email and billing work. Everything stays in your own Supabase project.

## Stripe and Resend [#stripe-and-resend]

Stripe owns checkout sessions, subscriptions, invoices and portal configuration. The app keeps a protected copy of each subscription's state, updated from verified webhook events. Billing belongs to a workspace, and the server decides whether that workspace has paid access. [Subscriptions and webhooks](/docs/billing/subscriptions) covers that lifecycle.

Resend delivers transactional email. The starter publishes five source HTML templates and connects a signed Supabase email hook for Auth messages. Workspace invitations and other application emails use their own narrowly scoped server code. Editing a template file doesn't change sent emails until you publish it through Kickstart; see [email templates](/docs/email/templates-delivery).

## GitHub and Vercel [#github-and-vercel]

Git tracks your changes, GitHub stores the private repository, and Vercel builds and runs the deployed app. Deployment Kickstart sets up their initial connection and uploads the runtime settings your app needs. After that, pushing to `main` deploys new versions.

Your setup folder keeps `.kickstart` and `.secrets` records that Kickstart uses to recognize the resources it created. They stay out of the repository. See [Deployment Kickstart](/docs/start/deployment-kickstart) for the first deployment and [Updates and recovery](/docs/operations/deployment) for later changes.

## Fumadocs, validation and AI [#fumadocs-validation-and-ai]

Fumadocs turns MDX into documentation pages with navigation, search, headings and Markdown actions. The product docs are files in your repository; the blog reads published posts from Supabase. Keep those two authoring paths separate when adding public content.

Zod validates structured input at application boundaries. Next.js route handlers serve the REST API, and the MCP SDK exposes the same operations as assistant tools. Both authenticate the caller and enforce explicit workspace permissions. Adding a field to a schema doesn't grant access to another workspace.

The AI example uses the Vercel AI SDK with the OpenRouter provider for streamed responses. The provider key stays on the server, alongside credit and request checks. The [AI guide](/docs/integrations/ai) covers configuration, enabling chat and removing it while keeping Auth, workspaces and billing.

## Follow a workspace mutation [#follow-a-workspace-mutation]

Workspace invitations show how a change travels through the app:

1. A form in the workspace UI sends the explicit workspace `org_id` and the selected recipient and role.
2. `inviteMember()` in `app/actions/workspaces.ts` gets the verified user through `requireUser()` and validates the IDs and role.
3. The signed-in user's Supabase client calls `issue_workspace_invitation`.
4. The database checks current membership, role, rate limits and invitation state. Hidden buttons are not the permission boundary.
5. The server sends the saved invitation through `lib/email/invitation.mjs` and records the outcome of that send attempt.
6. `revalidatePath('/dashboard/workspace')` refreshes the page. Resend accepting a message doesn't confirm it reached the inbox.

`lib/auth.ts` centralizes `requireUser()` and `getWorkspace(target?)`. The latter checks an explicit target against the user's current memberships. Never use a browser-supplied workspace ID in an elevated query without an equivalent authorization check.

## Daily commands [#daily-commands]

| Command               | Use                                                          | Effect on your services                                         |
| --------------------- | ------------------------------------------------------------ | --------------------------------------------------------------- |
| `npm ci`              | Install pinned dependencies and generate the docs collection | Downloads packages only                                         |
| `npm run dev`         | Run the app locally                                          | Using the app reads and writes your hosted services             |
| `npm run typecheck`   | TypeScript validation                                        | None                                                            |
| `npm run lint`        | Static code checks                                           | None                                                            |
| `npm run build`       | Production compilation                                       | None; a passing build doesn't test provider flows               |
| `npm test`            | Setup and failure checks                                     | Uses fake providers; no live calls                              |
| `npm run test:ui`     | Browser tests against a running server                       | Review what each suite does first                               |
| `npm run test:hosted` | SQL isolation checks                                         | Runs rolled-back test transactions on a chosen Supabase project |

Restart the development server after changing `.env`. If you change `APP_URL`, change it consistently: `localhost` and `127.0.0.1` count as different addresses for cookies and sign-in links.

## Development previews [#development-previews]

`/preview` renders development-only example states, and `/setup` helps with local configuration. They don't bypass `/dashboard` sign-in or show real provider behavior. For example, a preview of an active billing card has disabled payment controls. Use previews for layout work, and test real behavior with a signed-in test account.

## Work with a coding assistant [#work-with-a-coding-assistant]

Give the assistant a concrete outcome, the relevant files and the permission rule, for example: "Add a note visible only to members of its workspace; owners and admins can create one; test that other workspaces can't read or write it." Ask it to inspect the current source and installed framework docs before choosing helpers.

Keep credentials in `.env` rather than pasting them into the conversation. Your repository's `AGENTS.md` gives the assistant project rules; review it and add your own. Don't ask an assistant to reset a shared database or rewrite applied migrations to make a test pass.

## Dependencies, formatting and verification [#dependencies-formatting-and-verification]

`package.json` and `package-lock.json` belong together. Review upgrades deliberately, update the lockfile through npm and rerun the affected checks. `npm run format` formats TypeScript files only; it doesn't touch SQL, JSON or MDX. The starter doesn't include a database-type generator or CI pipeline, so add your own if you need them.

After a change, test the real path, including wrong roles, stale forms, failure states and recovery. The [first workspace feature](/docs/build/first-feature) is a worked example.