Capital project launchpad built with Next.js App Router, React 19, TypeScript, Tailwind CSS v4, shadcn/ui, Convex, WorkOS AuthKit, and WorkOS Widgets.
The starter is opinionated in a few ways:
- WorkOS is the source of truth for identity, organizations, and role claims.
- Convex stores only app-specific data: profiles, mirrored workspaces, projects, intake profiles, generated outputs, and generation audit events.
- The app is server-first. Client components are limited to auth bridging, widget surfaces, and realtime project interactions.
- Admin routes are protected both in navigation and on the server.
- Public marketing page at
/ - Hosted WorkOS auth routes:
/sign-in,/sign-up,/auth/callback - Protected application shell at
/app - Org-scoped workspace route at
/w/[slug] - First-login workspace onboarding at
/onboarding/workspace - WorkOS User Profile widget at
/settings/profile - WorkOS User Management widget at
/admin/users - WorkOS Admin Portal SSO Connection widget at
/admin/sso - Server-issued widget tokens at
/api/workos/widget-token/[widget]
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS v4
- shadcn/ui
- Convex
- WorkOS AuthKit for session handling
- WorkOS Widgets for profile, user admin, and SSO setup
Copy .env.example to .env.local and fill in the values:
cp .env.example .env.localRequired values:
WORKOS_API_KEY=
WORKOS_CLIENT_ID=
WORKOS_COOKIE_PASSWORD=
NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/auth/callback
NEXT_PUBLIC_CONVEX_URL=
CONVEX_DEPLOYMENT=
CONVEX_DEPLOY_KEY=Notes:
- WorkOS values must come from your WorkOS environment. The CLI can confirm active environment metadata, but do not scrape or print secrets while setting up
.env.local. WORKOS_COOKIE_PASSWORDshould be a strong 32+ character secret.NEXT_PUBLIC_CONVEX_URLandCONVEX_DEPLOYMENTare populated automatically afterconvex devorconvex deploy.CONVEX_DEPLOY_KEYis only needed for CI or production deploy automation.
Install dependencies with corepack pnpm:
corepack pnpm installStart the web app and Convex together:
corepack pnpm devOr run them separately:
corepack pnpm dev:web
corepack pnpm dev:convexUseful commands:
corepack pnpm lint
corepack pnpm typecheck
corepack pnpm build
corepack pnpm test:unit
corepack pnpm test:smoke
corepack pnpm test
corepack pnpm convex:codegen
corepack pnpm convex:deployThe first time you run the smoke suite locally, install the Playwright browser:
corepack pnpm exec playwright install chromiumThis starter uses hosted WorkOS AuthKit flows, not custom sign-in pages.
Protected routes are enforced in proxy.ts for:
/app/w/settings/admin/onboarding/api/workos/widget-token
Public routes remain available for:
//sign-in/sign-up/auth/callback
convex.json is configured to use Convex-managed AuthKit defaults across environments:
- Local development uses
http://localhost:3000/auth/callback - Preview deployments use
https://${VERCEL_BRANCH_URL}/auth/callback - Production uses
https://${VERCEL_PROJECT_PRODUCTION_URL}/auth/callback
convex/auth.config.ts validates WorkOS-issued JWTs in Convex. That allows:
- authenticated Convex queries and mutations
- org-scoped realtime project data
- server-side profile syncing for the current viewer
If you prefer to manage WorkOS configuration manually, keep the same redirect URI pattern and ensure the WorkOS client matches the Convex JWT settings.
WorkOS organizations are the tenancy authority. Convex mirrors each active organization into a workspaces record so the app can attach internal metadata and query by slug.
The data model is organized around the Capital Project Launchpad MVP:
profiles: cached app profile for the signed-in userworkspaces: mirrored organization metadataprojects: org-scoped capital project recordsprojectProfiles: resumable project intake datadeliveryAnalyses: generated delivery recommendation recordsprocurementPackages: generated procurement output recordsgenerationEvents: audit trail for generated content
There is no membership table in Convex. Membership and role data stay in WorkOS. Every app-data query and mutation derives the active organization from WorkOS JWT claims and rejects cross-organization access.
The starter expects these WorkOS roles:
owneradminmember
Access rules:
owner: full app access plus widget admin scopesadmin: app admin access plus widget admin scopesmember: standard app access only
Widget scopes used by the starter:
- User Profile: no extra scope
- User Management:
widgets:users-table:manage - SSO Connection:
widgets:sso:manage
Make sure your WorkOS roles grant the matching permissions, or the admin pages and widget token endpoint will correctly reject access.
Use the live WorkOS and Convex contracts directly. Do not store active workspace,
organization, role, permissions, or project data in localStorage.
Server-rendered app routes should start with the helpers in lib/server/auth.ts:
getAppContext()for signed-in pages that need the active workspace, viewer, role, and WorkOS permissions.requireWorkspaceRouteContext(slug)for/w/[slug]and/w/[slug]/projectsroutes.requireProjectRouteContext({ workspaceSlug, projectSlug })for project detail routes such as intake, delivery, procurement, and exports.requireAdminContext()for admin-only pages.
Server Components should use fetchQuery or preloadQuery with
context.auth.accessToken. Client Components should be rendered under
AppProviders and use Convex hooks such as usePreloadedQuery and
useMutation; Convex and WorkOS still enforce the real authorization checks.
Pure frontend helpers live in:
lib/routes.tsfor/app, workspace project, and project-section URL builders.lib/frontend-contracts.tsfor project/output status labels, role labels, permission affordances, user-facing error normalization, and shared data-contract types.
Route expectations for the Capital Project Launchpad UI phases:
/app: authenticated overview and entry point./w/[slug]/projects: active workspace project list./w/[slug]/projects/[projectSlug]/intake: project intake/profile./w/[slug]/projects/[projectSlug]/delivery: delivery analyses./w/[slug]/projects/[projectSlug]/procurement: procurement packages./w/[slug]/projects/[projectSlug]/exports: approved output exports.
Unknown workspace or project slugs should resolve to notFound(). Known but
inactive workspaces should route through /auth/activate-organization so the
WorkOS session organization changes before Convex data is queried.
Deploy on Vercel with the same environment variables used locally.
At minimum:
- Set the WorkOS and Convex environment variables in Vercel.
- Keep the preview and production callback URLs aligned with
convex.json. - Keep the Vercel build command as
corepack pnpm vercel-build; it runsconvex deploy --cmd-url-env-var-name NEXT_PUBLIC_CONVEX_URL --cmd "corepack pnpm build"so the backend schema/functions and Next.js app are published together.
CONVEX_DEPLOY_KEY is required in Vercel for this non-interactive deployment path.
app/
(marketing)/ public marketing shell
(app)/ authenticated app shell
onboarding/workspace/ first-org provisioning flow
api/workos/ server-issued widget tokens
components/
app/ sidebar shell and workspace UI
forms/ onboarding forms
widgets/ WorkOS widget client islands
convex/
schema.ts profiles, workspaces, projects, generated-output tables
users.ts viewer and profile sync
workspaces.ts workspace mirror queries/mutations
projects.ts project list/create/rename
projectProfiles.ts resumable intake persistence
deliveryAnalyses.ts delivery recommendation records
procurementPackages.ts procurement output records
generationEvents.ts generation audit trail
lib/server/
auth.ts app context and route guards
workos.ts WorkOS client, role checks, widget token helpers
lib/
routes.ts app/workspace/project URL builders
frontend-contracts.ts frontend data labels, permissions, and error helpers
These checks should pass after .env.local has real WorkOS values:
corepack pnpm lintcorepack pnpm typecheckcorepack pnpm buildcorepack pnpm test:unitcorepack pnpm test:smoke
Expected signed-out behavior:
/renders the marketing shell without browser runtime errors./appredirects to/sign-in?returnTo=%2Fapp./onboarding/workspaceredirects to/sign-in?returnTo=%2Fonboarding%2Fworkspace./api/workos/widget-token/user-profilereturns401JSON when signed out./sign-inand/sign-upstill hand off to WorkOS and may end on an invalid-client page until real credentials are configured.
Once real WorkOS credentials are configured, manually verify:
- sign-up, sign-in, callback, and sign-out
- first-workspace creation
- active organization switching
- project create and rename flows
- member vs admin access to
/admin/usersand/admin/sso - widget rendering with server-issued tokens