Everything you need to ship a full-stack single-page app and optional desktop build. A modern starter for React + Vite + Hono with shadcn/ui, designed for AI-assisted refactors and deploy-anywhere flexibility.
- Framework: React 19 with Vite 7
- Language: TypeScript 5.9
- Auth: Clerk (sign-in, sign-up, protected routes, JWT-secured API)
- UI Library: shadcn/ui (new-york style) + Lucide React icons
- Styling: Tailwind CSS v4
- Routing: React Router v7 (SPA)
- Backend: Hono (Node runtime; portable to Cloudflare Workers, Deno, Bun)
- Database: PostgreSQL with Drizzle ORM
- Desktop (optional): Tauri v2
- Monorepo: npm workspaces (
apps/web+apps/api)
- ✅ React 19 + Vite 7 — Fast SPA with HMR and simple build pipeline
- ✅ Hono API — Type-safe backend with RPC-style types flowing to the frontend
- ✅ Drizzle + PostgreSQL — Type-safe ORM with migrations and Drizzle Studio
- ✅ Tailwind CSS v4 — Utility-first styling with a single config
- ✅ shadcn/ui + Lucide — Button, theme variables,
cn()utility; add more withnpx shadcn@latest add <name> - ✅ Sidebar layout — Ready-made app shell with nav (Home, Settings) and main content area
- ✅ Typed API client — End-to-end types via Hono RPC; no codegen
- ✅ Deploy anywhere — Node runs on Railway, Render, Fly, and most hosts; same API can target Workers
- ✅ Clerk authentication — Sign-in, sign-up, and protected routes; API protected with JWT verification
- ✅ Optional desktop — Tauri v2 for a native shell around the same React app
- ✅ One repo, two apps — AI (and you) get a full view of frontend and backend; deploy together or separately
Before you begin, make sure you have the following installed:
- Node.js (version 18 or higher; 20+ recommended) — Download here
- npm (comes with Node.js)
- PostgreSQL (for the API database) — Download here or use a hosted service (Neon, Supabase, Railway, etc.)
- Rust (only if you use Tauri) — Install guide
To check your versions:
node --version # 18.0 or higher
npm --versionShortest path to a running app on macOS with Homebrew:
-
Install dependencies
brew install node postgresql@17 brew services start postgresql@17
Optional: portless for stable dev URLs (e.g.
http://launchkit.localhost:1355). Install and start the portless proxy beforenpm run devif you use it. -
Clone, install, database
git clone https://github.com/aculich/launch-kit-clean.git my-app && cd my-app npm install createdb launchkit
Copy env:
cp .env.example .env(or use 1Password inject — see Managing credentials with 1Password). SetDATABASE_URL=postgresql://localhost:5432/launchkitin.env, then:npm run db:push --workspace=@launch-kit-spa-desktop-switchdimension/api
-
Clerk
Create an app at clerk.com, copy Publishable and Secret keys into.envasVITE_CLERK_PUBLISHABLE_KEY,CLERK_PUBLISHABLE_KEY, andCLERK_SECRET_KEY(see Set up Clerk). -
Run
npm run dev
Open http://localhost:5167. If you use portless, start it first and open http://launchkit.localhost:1355 instead.
To stay in sync with the upstream template: git remote add upstream https://github.com/switch-dimension/launch-kit-spa-desktop-switchdimension.git (or the original repo URL), then git fetch upstream && git rebase upstream/main when you want to pull in updates.
git clone <your-repo-url> my-app
cd my-appnpm installCopy the example env file:
cp .env.example .envYou need a PostgreSQL database. Set one up locally or in the cloud with NeonDB or Supabase:
- Local Postgres (one instance, one DB per project): Use a single Postgres install and create one database per app (e.g.
launchkitfor this repo). Portless only proxies HTTP (Vite/API); the API connects to Postgres viaDATABASE_URLonlocalhost:5432, so no portless config is needed for the DB.- macOS (Homebrew): Start Postgres, create a DB, set
.env:Inbrew services start postgresql@17 # or postgresql@16, etc. createdb launchkit # one DB per project
.env:DATABASE_URL=postgresql://localhost:5432/launchkit(no password needed for default local trust auth). Then runnpm run db:push --workspace=@launch-kit-spa-desktop-switchdimension/api. - Other local setups: Install PostgreSQL, create a database, and set
DATABASE_URLin.env(e.g.postgresql://user:password@localhost:5432/your_db).
- macOS (Homebrew): Start Postgres, create a DB, set
- NeonDB: If you have the Neon CLI installed, ask your AI agent to create a new database and save the connection string to
.env. Or create a project at neon.tech, copy the connection string from the dashboard, and paste it into.env. - Supabase: Create a project at supabase.com, go to Settings → Database, copy the connection string (URI format), and paste it into
.env.
Once DATABASE_URL is set, run migrations from the API package (or use db:push for prototyping):
npm run db:push --workspace=@launch-kit-spa-desktop-switchdimension/api
# Or: npm run db:migrate --workspace=@launch-kit-spa-desktop-switchdimension/apiThe app uses Clerk for authentication. Without keys the app shows a "Clerk not configured" screen with setup instructions.
No CLI or API for creating applications. Clerk does not provide a public CLI or REST API to create a new Clerk application or to obtain the publishable/secret key pair (pk_test_ / sk_test_) programmatically. Creating an application and copying API keys is a one-time manual step in the Clerk Dashboard. The Clerk CLI (early access) is for using an existing secret key to manage resources, not for creating applications.
-
Create a free account at clerk.com and create an application. When prompted to choose a framework, select React.
-
In the Clerk Dashboard, go to API Keys. The dashboard will show keys using Next.js naming conventions:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_...This project uses Vite, not Next.js, so the variable names are different. Copy the key values and set them in
.envusing the names below:Clerk Dashboard shows Set in .envasUsed by NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...VITE_CLERK_PUBLISHABLE_KEY=pk_test_...Vite frontend (same publishable key value) CLERK_PUBLISHABLE_KEY=pk_test_...Hono API ( @hono/clerk-auth)CLERK_SECRET_KEY=sk_test_...CLERK_SECRET_KEY=sk_test_...Hono API (JWT verification) The publishable key appears twice with different prefixes:
VITE_is required for Vite to expose it to the browser, and the unprefixedCLERK_PUBLISHABLE_KEYis what the API middleware expects. Use the samepk_test_...value for both. -
The Publishable Key is all you need to get sign-in and sign-up working. The Secret Key is needed for the API to verify JWTs (protect
/api/todos,/api/users, etc.). -
If you can't see the Secret Key, look for a "Reveal" or "Show" button next to it on the API Keys page. Only account Admins can reveal it.
-
Ensure your Clerk application's allowed redirect URLs include your dev URLs. Add at least:
http://localhost:5167(direct Vite port)- If you use portless:
http://launchkit.localhost:1355(and optionallyhttps://launchkit.localhost:1355). Add these in Clerk Dashboard → Configure → Paths (or Settings → Domains / redirect allowlist).
Important: When you first open the app, click Sign up to create an account before trying to sign in. There are no existing users until you register one.
Note: The
.envfile lives in the project root (same folder aspackage.json). Vite is configured to load env vars from there viaenvDirinapps/web/vite.config.ts. Do not put your.envinsideapps/web/.
If you use the 1Password CLI (op), keep one 1Password item per app (e.g. "Launchkit Clerk") in a vault like develop, and reference secrets in a template file so you never commit real values.
- Template: Keep
.env.templatewithop://references (e.g.op://develop/Launchkit Clerk/VITE_CLERK_PUBLISHABLE_KEY). Do not commit.env. - Inject: Run
OP_ACCOUNT=my.1password.com op inject -i .env.template -o .envto write resolved values into.env. Use the same account prefix for allopcommands if your setup requires it. - Best practice: One item per app; use
op injectand avoid workarounds. Ifopfails (e.g. not signed in), fix auth or the item — don’t paste secrets into the repo. - After inject, set any local-only vars in
.env(e.g.DATABASE_URL=postgresql://localhost:5432/launchkit).
npm run dev- Web app: http://localhost:5167 — or, if you use portless, http://launchkit.localhost:1355 (portless proxies to 5167).
- API: http://localhost:3834 (e.g. http://localhost:3834/api/health); with portless, http://api.launchkit.localhost:1355 → 3834.
Open the web app URL in your browser. The frontend proxies /api to the API in development, so you don’t need CORS. After changing .env (e.g. adding Clerk keys), restart the dev server (Ctrl+C, then npm run dev) and hard-refresh the browser (Cmd+Shift+R / Ctrl+Shift+R) or use an incognito window.
With Rust installed:
npm run tauriTauri starts the web app and opens a native window. Build a production desktop binary with npm run tauri:build.
| Command | What it does |
|---|---|
npm run dev |
Runs the web app (5167) and API (3834) together. |
npm run dev:web |
Web app only. |
npm run dev:api |
API only. |
npm run build |
Builds the web app and API. |
npm run lint |
Lints the frontend. |
npm run tauri |
Starts the Tauri dev window (loads the app from 5167). |
npm run tauri:build |
Builds the desktop app. |
npm run db:generate -w @launch-kit-spa-desktop-switchdimension/api |
Generates Drizzle migrations from schema. |
npm run db:migrate -w @launch-kit-spa-desktop-switchdimension/api |
Runs Drizzle migrations. |
npm run db:push -w @launch-kit-spa-desktop-switchdimension/api |
Pushes schema to DB (prototyping). |
npm run db:studio -w @launch-kit-spa-desktop-switchdimension/api |
Opens Drizzle Studio. |
├── apps/
│ ├── web/ # Frontend SPA
│ │ ├── src/
│ │ │ ├── app/ # Layout, sidebar, route config
│ │ │ ├── features/ # Feature modules (add your own)
│ │ │ ├── shared/ # Components, hooks, lib (e.g. API client)
│ │ │ ├── components/ # shadcn/ui components
│ │ │ └── pages/ # Route-level pages
│ │ └── ...
│ └── api/ # Hono API
│ ├── src/
│ │ ├── routes/ # Route modules (e.g. health, users)
│ │ └── lib/
│ │ └── db/ # Drizzle schema and client
│ ├── drizzle/ # Generated migrations
│ └── drizzle.config.ts
├── src-tauri/ # Tauri desktop app (optional)
├── .agents/skills/ # Agent skills (canonical path per [Cursor](https://cursor.com/docs/skills) / [Agent Skills](https://agentskills.io))
├── package.json # Workspaces + root scripts
└── tsconfig.base.json
.agents/skills/— Project-level agent skills (each subdir hasSKILL.md). Use this path only;.agent/skills/(singular) is non-standard.app/— Shell: layout, sidebar, and routing. Change once, everything updates.features/— Domain or product features; each can own components, hooks, and state.shared/— Reusable UI and utilities; no imports fromfeaturesorpages.pages/— Thin route components that compose features.
# Build frontend and API
npm run buildDeploy apps/web/dist to any static host or CDN, and run the API (e.g. node dist/index.js from apps/api, or your host’s Node runtime). Or run both in a single Railway (or similar) container.
You can deploy this repo to Render or Railway and then wire up Clerk (and optional custom domains). Use one Clerk production instance for both platforms: add all deployment URLs (Render, Railway, and any custom domains) to Clerk's allowed redirect/sign-in URLs. That is not problematic—Clerk supports multiple origins per application.
The repo includes a Render Blueprint (render.yaml) that defines a Postgres database, a Node API service, and a static site (Vite SPA). For editing the Blueprint and deploy flows in Cursor, the Render Cursor plugin (Cursor Marketplace) is recommended.
The deploy button above points at this repo (
mainbranch). To deploy from the original template instead, userepo=https://github.com/switch-dimension/launch-kit-spa-desktop-switchdimension/tree/mainin the deploy link. Render says "render.yaml not found"? Grant the Render GitHub App access to this repo (Repository access → selectaculich/launch-kit-clean), then Retry.
Monorepo note: render.yaml lives at the repository root (top-level), not inside apps/api or apps/web. The Blueprint Path is always relative to the repo root—use render.yaml. Each service’s Root Directory in the Blueprint (e.g. rootDir: apps/api for the API) only controls where that service’s build/start run; it does not change where Render looks for the Blueprint file. Do not set Blueprint Path to a subdirectory.
- Click the button and connect the repo. On the Blueprint form, set Blueprint Name to
Launchkit Template(or any name). The Blueprint Path field is often not prefilled—typerender.yamlso Render finds the file at the repo root. Then create the Blueprint. Render will createlaunchkit-db,launchkit-api, andlaunchkit-web.- If you see "A Blueprint file was found, but there was an issue": Confirm Blueprint Path is exactly
render.yaml(repo root) and that the connected repo/branch containsrender.yamlat the top level. Fix the path if needed and try again, or use Manual Sync after correcting. - **If you see "Blueprint file render.yaml not found on main branch": Render’s GitHub App may not have access to this repo yet. Fix: Open github.com/apps/render/installations/new (or your existing Render app installation). Under Repository access, add or select this repo (
aculich/launch-kit-clean) so Render can read it. Save, then on the Render Blueprint page click Retry. Also confirm Branch ismain, Blueprint Path isrender.yaml, and there is no.render-ignoreat repo root. Alternative: in the Render Dashboard go to New → Blueprint, connect GitHub, and pick this repo manually—that flow can trigger the right permissions.
- If you see "A Blueprint file was found, but there was an issue": Confirm Blueprint Path is exactly
- On the Blueprint review step (or in the Dashboard after deploy), set environment variables. They are marked sync: false in the Blueprint so you provide values once:
- launchkit-api:
CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY(use your Clerk production keys —pk_live_...andsk_live_...). - launchkit-web:
VITE_CLERK_PUBLISHABLE_KEY(same publishable key),VITE_API_URL(your API’s public URL, e.g.https://launchkit-api.onrender.com— use the URL Render shows for thelaunchkit-apiservice; it isn’t auto-filled because the Blueprint only exposes private-network references). - 1Password (Launchkit Clerk): To have the 1Password browser extension suggest fills on the Render form, add custom fields to your "Launchkit Clerk" item (develop vault) with the exact labels Render uses:
CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY,VITE_CLERK_PUBLISHABLE_KEY. Put the publishable key in the first and third; put the secret key in the second. Optionally addVITE_API_URLand set it after your first deploy. The extension matches by field label, so matching names improve autofill.
- launchkit-api:
- In the Clerk Dashboard (production instance), add your Render URLs to Configure → Paths (or Domains / redirect allowlist): e.g.
https://launchkit-web.onrender.comand your API URL if needed for redirects. - Redeploy the web service after setting
VITE_API_URLso the frontend is built with the correct API base URL.
Verify: In the Render Dashboard you should see launchkit-db, launchkit-api, and launchkit-web. Open the web service URL (e.g. https://launchkit-web.onrender.com) and the API health URL (e.g. https://launchkit-api.onrender.com/api/health) to confirm the app and API are reachable; then sign in to confirm Clerk redirects.
Railway uses templates for one-click deploy. Create a template from this project, then add the deploy button to your README.
-
In Railway, create a new project from this repo (or your fork). Add a Web Service for the API (root dir
apps/api, buildnpm install && npm run build, startnpm start) and a Static Site (or second service) for the frontend (build from root, outputapps/web/dist). Add a Postgres plugin and connect it to the API viaDATABASE_URL. Configure Clerk env vars as above. -
In Workspace → Templates, use Generate Template from Project to create a template. Copy the template URL.
-
Add the deploy button to your README (replace
YOUR_TEMPLATE_IDwith the ID from the template URL):[](https://railway.com/new/template/YOUR_TEMPLATE_ID?utm_medium=integration&utm_source=button&utm_campaign=launchkit)
-
In the Clerk Dashboard (production instance), add your Railway URLs (e.g.
https://your-app.up.railway.app) to the allowed redirect/sign-in list.
- Same Clerk for Render and Railway: Use one Clerk production instance; add every deployment origin (Render, Railway, and any custom domains) to Clerk's allowed URLs. See Deploy your Clerk app to production.
- Custom domains (e.g. DNSimple): For
flashmobcluster.com/flashmobcluster.org, add CNAME (or A) records for your chosen subdomains (e.g.launchkit.flashmobcluster.com→ Render/Railway). Then addhttps://launchkit.flashmobcluster.com(and API subdomain if used) to Clerk. DNSimple API credentials are in 1Password develop vault (DNSIMPLE_API_ACCOUNT,DNSIMPLE_API_KEY) for automation; if you have other services on the same domain, use distinct subdomains to avoid conflicts.
| Piece | Choice | Why |
|---|---|---|
| Build | Vite | Fast dev server and builds; works the same for web and Tauri. |
| UI | React 19 + TypeScript | Familiar, strong ecosystem; TypeScript keeps the app and API in sync. |
| Styling | Tailwind CSS v4 | Utility-first, quick to refactor; one config for the whole UI. |
| Routing | React Router v7 | SPA routing with a simple, stable API. |
| API | Hono | Small, fast, type-safe; runs on Node now and can move to Cloudflare Workers, Deno, or Bun with a different adapter. |
| Database | Drizzle + PostgreSQL | Type-safe schema and queries; migrations via Drizzle Kit; works with any Postgres (local, Neon, Supabase, etc.). |
| Auth | Clerk | Hosted auth with prebuilt UI components; JWT verification on the API via @hono/clerk-auth. |
| Types | Hono RPC | The API exports its route types; the frontend gets a typed api client with no codegen. |
| Desktop | Tauri v2 | Same HTML/JS/CSS as the web app; small binaries and system access when you need it. |
| UI components | shadcn/ui | Copy-paste components (Radix primitives + Tailwind); theme via CSS variables. |
| Icons | Lucide React | Consistent icon set; used by shadcn and across the app. |
The repo is a monorepo (npm workspaces): frontend and API are separate packages so you can deploy or scale them independently, while still sharing types and running everything with one npm run dev.
I usually reach for Next.js, but I find myself building more and more single-page applications. Next is excellent at static rendering and has a rich ecosystem, but it comes with a lot of APIs and opinions. I want to refactor quickly with AI, host and store the app anywhere, and often ship a desktop build—and I don’t always need the serverless model Next optimizes for. For those cases, a lean SPA fits better.
On the frontend, we use React, Vite, Tailwind, and shadcn/ui. These are widely understood by AI tools and have a strong, predictable developer experience, so refactors and feature work stay fast.
On the backend, we use Hono instead of Express. Hono’s typing and design make it very well suited to working with modern AI: the codebase is easy for models to reason about, and the RPC-style types flow cleanly to the client. We run it on Node for maximum compatibility—Railway, Render, Fly, and almost every other host support it, so you can deploy the API wherever you like. Because Hono is runtime-agnostic, you can later move the same app to Cloudflare Workers (or other edges) for a very cheap, globally distributed backend if that fits your product.
Everything lives in one repository with two clear folders (apps/web and apps/api). You can deploy them separately—frontend to a CDN or static host, API to Node or Workers—or run both in a single Railway (or similar) container: one process serves the API, another serves the built frontend, and they talk over the network. Keeping both sides in one repo means an AI assistant has a complete view of the application and can change frontend and backend together. It also means the API can evolve into a standalone service: other clients (another app, an MCP server, or an agent) can talk to the same backend without being tied to this UI. In an agent-first world, having a first-class, deployable API that isn’t locked to a single framework is important.
The app uses shadcn/ui (new-york style, Radix) and Lucide React for icons. Theme variables live in apps/web/src/index.css (dark by default; add class light on <html> for light mode).
- Add components: From
apps/web, runnpx shadcn@latest add <component>(e.g.button,card,dialog). Components go intosrc/components/ui/. - Use components:
import { Button } from "@/components/ui/button". - Use icons:
import { Home, Settings } from "lucide-react". cn()helper:import { cn } from "@/lib/utils"to merge Tailwind classes.
The app ships with Clerk wired end-to-end:
- Frontend:
ClerkProviderwraps the app inmain.tsx. Routes/sign-inand/sign-uprender Clerk's prebuilt components. All other routes are wrapped in aProtectedRoutethat redirects unauthenticated users to/sign-in. The sidebar shows aUserButtonwhen signed in. - API:
clerkMiddleware()from@hono/clerk-authis applied to all/api/*routes. Protected route handlers (todos, users) callgetAuth(c)and return401when no valid session is present./api/healthremains public. - Authenticated API calls: Use the
useApi()hook from@/shared/lib/use-api— it returns a Hono RPC client that automatically attaches the Clerk session JWT as aBearertoken.
| Variable | Where it's used | Required |
|---|---|---|
VITE_CLERK_PUBLISHABLE_KEY |
Frontend (Vite exposes it to the browser) | Yes — app won't load without it |
CLERK_PUBLISHABLE_KEY |
API (@hono/clerk-auth middleware) |
Yes — API fails silently without it |
CLERK_SECRET_KEY |
API (JWT verification) | Yes — API returns 401 without it |
All three go in the root .env file. VITE_CLERK_PUBLISHABLE_KEY and CLERK_PUBLISHABLE_KEY use the same pk_test_... value.
- No Publishable Key: The app shows a full-page "Clerk not configured" screen with step-by-step setup instructions.
- No Secret Key: The frontend works (sign-in, sign-up, navigation), but all API calls to protected routes return
401 Unauthorized.
In-app errors: Pages that call the API (e.g. Todos) show setup and API errors in the UI (not only in the console). If you see "Sign-in required" or "Request failed (401)", add the Clerk keys to .env and restart the API; for 500 errors, check DATABASE_URL and that the database is running. The app guides you through each step of the devops setup where human intervention is needed.
The API exports its type; the frontend gets a typed client with no codegen:
// Unauthenticated client (for public routes like /api/health)
import { api } from '@/shared/lib/api-client';
const res = await api.api.health.$get();
const data = await res.json(); // { status: 'ok' } — typed// Authenticated client (for protected routes — use inside React components)
import { useApi } from '@/shared/lib/use-api';
function MyComponent() {
const api = useApi(); // attaches Clerk JWT automatically
// ...
const res = await api.api.todos.$get();
}Add routes in apps/api/src/routes/ and mount them in apps/api/src/index.ts; the client types update automatically.
The API uses Drizzle ORM with PostgreSQL. Schema lives in apps/api/src/lib/db/schema.ts; the client is in apps/api/src/lib/db/index.ts.
- Env: Set
DATABASE_URLin.env(see.env.example). - Schema: Edit
apps/api/src/lib/db/schema.tsand add tables withpgTable,serial,text,timestamp, etc. - Migrations: Run
npm run db:generate -w @launch-kit-spa-desktop-switchdimension/apito generate SQL, thennpm run db:migrateto apply. For quick prototyping, usenpm run db:push. - Studio: Run
npm run db:studio -w @launch-kit-spa-desktop-switchdimension/apito open Drizzle Studio and inspect or edit data. - In routes: Import
dbfrom../lib/db/index.jsand usedb.select(),db.insert(), etc. Example:GET /api/usersinapps/api/src/routes/users.ts.
The same React app runs in a Tauri window. No separate “desktop” UI. Config lives in src-tauri/tauri.conf.json (dev URL, build commands, icons). You need Rust installed to use Tauri.
- Clone or fork this repo and rename it (e.g.
my-product). - Search and replace any remaining project-specific names in:
package.json(name)apps/web/index.html(title)src-tauri/tauri.conf.json(productName, identifier, window title)- Set
DATABASE_URLin.envfor your database.
- Set up Clerk: Create a Clerk application and add
VITE_CLERK_PUBLISHABLE_KEY,CLERK_PUBLISHABLE_KEY, andCLERK_SECRET_KEYto.env(see Set up Clerk for the key name mapping). - Customize the sidebar in
apps/web/src/app/layout/Sidebar.tsx(nav items, branding). - Add routes in
apps/web/src/app/routes.tsxandapps/api/src/routes/, and use the sharedapiclient (oruseApi()for authenticated calls) in the UI. - Optionally remove Tauri if you only need web: delete
src-tauri/, drop@tauri-apps/apifrom the web app, and remove thetauri/tauri:buildscripts from the rootpackage.json.
You can keep the API and run the frontend as a static site, or deploy both; the structure stays the same.
| Service | Port | Override |
|---|---|---|
| Web (Vite) | 5167 | server.port in apps/web/vite.config.ts |
| API (Hono) | 3834 | PORT env var or default in apps/api/src/index.ts |
If you change the API port, update the proxy in apps/web/vite.config.ts and (for production) VITE_API_URL if you use it.
Port already in use (EADDRINUSE)
- Another process is using 5167 or 3834. Stop other dev servers or kill the process:
lsof -ti:5167 | xargs kill lsof -ti:3834 | xargs kill
- Then run
npm run devagain.
API types not resolving in the frontend
- Ensure
npm installhas been run from the repo root so the@launch-kit-spa-desktop-switchdimension/apiworkspace is linked. - The API package exposes
mainandtypesinapps/api/package.jsonpointing at./src/index.ts.
API fails to start or "DATABASE_URL is required"
- Copy
.env.exampleto.envand setDATABASE_URLto a valid PostgreSQL connection string (e.g.postgresql://user:password@localhost:5432/dbname). - Ensure the database exists and migrations have been run (
npm run db:pushornpm run db:migratefrom the API workspace).
"Clerk not configured" screen
- You need
VITE_CLERK_PUBLISHABLE_KEYset in the root.envto a valid Clerk publishable key (starts withpk_test_and has more characters after it). - The
.envfile must be in the project root (next topackage.json), not insideapps/web/. - After editing
.env, restart the dev server (Ctrl+C, thennpm run dev). Vite only reads env files at startup. - Open the browser console (F12 → Console) and look for the
[Clerk config]log to see what value the app received.
API returns 401 on all requests or fails silently
- Make sure all three Clerk variables are set in
.env:VITE_CLERK_PUBLISHABLE_KEY,CLERK_PUBLISHABLE_KEY, andCLERK_SECRET_KEY. The Clerk dashboard showsNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY— copy that value into bothVITE_CLERK_PUBLISHABLE_KEYandCLERK_PUBLISHABLE_KEY. CLERK_PUBLISHABLE_KEY(no prefix) is required by@hono/clerk-auth. If it's missing the API will throw "Missing Clerk Publishable key" on every request.CLERK_SECRET_KEYstarts withsk_test_. Find it on the Clerk Dashboard → API Keys page (click "Reveal" or "Show"; only Admins can see it).- Restart the dev server after editing
.env.
Tauri dev/build fails
- Install Rust: tauri.app/start/install.
- Ensure the web app builds:
npm run build --workspace=@launch-kit-spa-desktop-switchdimension/web.