Naalya Handbook

Getting Started

Get the school website running on your machine — prerequisites, install, a .env, and the dev server on :5173.

Getting the school website running is a ten-minute job once you know the two things the README won't tell you: this project runs on pnpm (not npm), and it needs a global CLI that isn't in package.json. Get those right and everything else is the usual install, .env, dev rhythm.

Before you start, hold one mental model in your head: the repo root is really two projects in one folder. The thing you boot here is the website frontend — React Router v7 in SSR mode, served by Vite on port 5173. The Sanity Studio lives in sanity/ with its own lockfile and its own dev server, and you start it separately. This page gets the frontend up; the Studio gets one step at the end and its own Setup page.

Don't trust the committed README

The repo's README.md is the stock "Welcome to React Router!" template. It tells you to run npm install / npm run dev and never mentions pnpm, the Studio, or the codegen CLI. Treat package.json and this handbook as the source of truth — and use pnpm, never npm/yarn/bun.


Step 1: Check your toolchain

Three tools need to be in place before you clone. None of them are pinned in the repo (there's no .nvmrc, engines, or packageManager field), so the versions below are convention — match them and you'll match production.

ToolTargetWhy this version
Node.js22 LTSAny Node ≥ 20 runs Vite 7 / React Router 7, but the Dockerfile pins node:22-alpine — match it to avoid surprises.
pnpm11The lockfile is lockfileVersion: 9.0. Enable it through Corepack.
chowbea-axios2.xA global CLI that generates the API client. Not a repo dependency — see Step 2.

The cleanest way to get the right pnpm is Corepack, which ships with Node and pins the version for you:

terminal
corepack enable
corepack prepare pnpm@11 --activate

If you hit native-module or Vite errors, check Node first

Because nothing in the repo enforces a Node version, the most common "works on my machine" gap is a mismatched Node. Align to 22 to match the Docker image before you debug anything deeper.


Step 2: Install the global codegen CLI

This is the step everyone misses. The site's typed API client is generated by a CLI called chowbea-axios, and that CLI is installed globally — it is not in package.json and won't appear in node_modules/.bin after pnpm install.

Install it once, globally:

terminal
pnpm add -g chowbea-axios

Skip this and every api:* script breaks

Without the global CLI, every pnpm api:* script (except the offline api:generate:fetch fallback) fails with command not found. You don't need to run codegen to boot the site — the generated files are committed — but the scripts are dead until this is installed. The full workflow lives in The API Client.


Step 3: Install frontend dependencies

From the repo root, the standard install pulls every frontend dependency:

terminal
pnpm install

This also runs the prepare script (husky) to wire up git hooks. Heads up: the only hook is a no-op — it echoes and exits, so it won't format or lint for you on commit. That's covered in Troubleshooting & Gotchas.

The Studio is not part of this install

pnpm-workspace.yaml has no packages: key, so sanity/ is excluded from the root workspace and carries its own pnpm-lock.yaml. Running pnpm install here installs the frontend only — you cannot reach the Studio with pnpm --filter. Step 6 installs it separately.


Step 4: Create your .env

The frontend reads its config from environment variables, all prefixed VITE_* so Vite exposes them to the browser. There's no template to copy, so you'll create the file by hand.

There is no .env.example

The repo ships no .env.example or .env.sample, and .env / .env.local are gitignored. You reconstruct it from scratch — start with the working baseline below.

Create a .env at the repo root with these essentials. They split into a few clear groups — the backend URL, the Sanity connection, and a couple of feature toggles:

.env
# --- Backend API (feeds the generated axios client) ---
VITE_API_URL=https://tunnel.chowbea.com
VITE_BASE_API_URL=https://tunnel.chowbea.com

# --- Sanity (must match the Studio's hardcoded values) ---
VITE_SANITY_PROJECT_ID=hdefozzv
VITE_SANITY_DATASET=development
VITE_SANITY_API_VERSION=2024-01-01
VITE_SANITY_WRITE_TOKEN=your-sanity-write-token

# --- App environment (drives the log level) ---
VITE_NODE_ENV=development

# --- Naalya-AI chat timers, in seconds (no defaults — set both) ---
VITE_NAALYA_AI_ACTIVE_TIME=300
VITE_NAALYA_AI_COOLDOWN_TIME=600

The few that actually matter for booting: VITE_API_URL is the backend the axios client points at, and the VITE_SANITY_* block is your CMS connection. PostHog analytics keys (VITE_PUBLIC_POSTHOG_*) are optional — leave them out until you need analytics. For the exhaustive inventory of every variable and where each is read, see The Stack.

Match Sanity values to the Studio — and note the dataset

The frontend reads Sanity config from env, but the Studio hardcodes projectId: "hdefozzv" and dataset: "development". Use those exact values. The default dataset is development, not production — a classic first-day trip-up.

There's one Vite subtlety worth internalizing now: VITE_* values are baked into the bundle at build time. In dev that's invisible, but it means you can't change them on a running production server — you rebuild. Keep it in mind for later.


Step 5: Start the dev server

With dependencies and .env in place, you're ready to boot:

terminal
pnpm dev

This runs react-router dev --host. Open http://localhost:5173 — that's the Vite default, and vite.config.ts doesn't override it. The --host flag also exposes the server on your LAN IP (0.0.0.0), handy for testing on a phone.

That's the site running. The other script you'll reach for is pnpm dev:all, which runs the API watcher and Vite together so the client regenerates whenever the backend spec changes — but you only need it when you're touching the API, so it's parked in The API Client.

A fresh clone boots without running codegen

The generated API files under app/services/api/_generated/ are committed, so the app type-checks and runs immediately. You only run pnpm api:fetch when the backend spec actually changes — not to get started.


Step 6: Run the Sanity Studio (separately)

The Studio is the second project in the folder, and because it's outside the workspace you install and run it from inside sanity/ with its own commands:

terminal
cd sanity
pnpm install      # uses the Studio's own lockfile
pnpm dev          # runs `sanity dev` on http://localhost:3333

The Studio opens on http://localhost:3333 and connects to the same hdefozzv / development project your frontend reads. You don't need it running to develop most pages — but you do need it to edit content, and to create any singleton documents (like the homepage) that the frontend expects to exist.

Full Studio configuration, the Google Drive asset source, and the tooling differences (the Studio uses Prettier + ESLint, not Biome) all live in Sanity Setup.


You're running — now what?

A quick recap of the four things that actually matter on day one:

pnpm, not npm

The repo is a pnpm 11 project. The README's npm instructions are wrong.

The global CLI

Install chowbea-axios globally or every api:* script fails.

Build your own .env

No .env.example exists — reconstruct it from the baseline above.

Two projects, two servers

Frontend on :5173, Studio on :3333, started separately.

Where to go next

On this page