Skip to main content

Project Structure

Place Nuxt UI, Convex functions, auth configuration, and shared policy with clear ownership.

After completing the authenticated golden path, the application has these boundaries:

my-convex-app/
├── app/
│   ├── pages/
│   │   ├── auth.vue              # sign-in and sign-up UI
│   │   └── index.vue             # protected todo page
│   └── composables/              # app-owned UI composition
├── convex/
│   ├── _generated/               # Convex codegen; do not hand-edit
│   ├── auth.config.ts            # Convex auth provider
│   ├── auth.ts                   # Better Auth factory and component client
│   ├── convex.config.ts          # Convex components
│   ├── http.ts                   # Better Auth HTTP routes
│   ├── schema.ts                 # application data schema
│   └── todos.ts                  # backend query, mutation, and authorization
├── server/
│   └── api/                      # only when a real Nitro boundary is needed
├── shared/                       # app-owned validators and policy types
├── .env.local                    # local public deployment URLs
├── nuxt.config.ts                # module and runtime behavior
└── package.json

Keep responsibilities close to their owner

app/

Components call the module composables and present state. UI capability checks may hide controls, but they do not authorize backend data.

convex/

Convex functions validate arguments, read identity, enforce product access, and update data transactionally.

Better Auth configuration also lives here because the Convex component owns the auth database and HTTP routes.

server/

Add a Nitro route for server-held secrets, external webhooks, or an intentional server API. Do not proxy ordinary Convex queries through Nuxt without a boundary that requires it.

shared/

Share pure validation schemas, role names, and types between frontend and backend when useful. The backend still performs the authoritative check.

Optional auth-client definition

Create convex-auth.ts at the Nuxt srcDir only when the browser client needs additional Better Auth plugin methods:

convex-auth.ts
import { defineConvexAuthClient } from 'better-convex-nuxt/auth-client'

export default defineConvexAuthClient({
  plugins: [],
})

The definition is for build-time typing and plugin registration. It is not a second runtime client.

Testing layout

Keep invariant tests near the owner:

  • Convex tests for authorization, ordering, and mutation invariants
  • Nuxt tests for composable lifecycle and route behavior
  • Browser tests for the signed-in product flow

Do not use a browser test as the only verification of backend authorization.