Skip to main content

Local development

This page is for developers who want to run the admin locally with hot module reload, write plugins or themes against live admin code, or contribute to the CMS itself.

If you just want to run the admin without modifying it, the no-build path is faster.

Prerequisites

  • Node.js 20+ (the project uses modern Node APIs and tests)
  • Git
  • A Firebase project + Flexweg account configured (same as the no-build path — see Installation overview)

Clone and install

git clone https://github.com/<owner>/flexweg-cms.git
cd flexweg-cms
npm install --legacy-peer-deps
--legacy-peer-deps is required

The project pins TypeScript 6 (matching the kanban sibling project's convention), while react-i18next declares an optional peer on TypeScript 5. The peer is optional but npm 7+ treats peer ranges strictly by default. The flag is safe — install completes correctly.

If you forget the flag you'll get an ERESOLVE error. Just re-run with --legacy-peer-deps.

Configure .env

Copy the example and fill in your Firebase credentials:

cp .env.example .env

Edit .env:

VITE_FIREBASE_API_KEY=AIza…
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=1234567890
VITE_FIREBASE_APP_ID=1:1234567890:web:abcdef

# Email of your bootstrap admin (must match the Firebase Auth user
# AND the email pinned in Firestore rules)
VITE_ADMIN_EMAIL=[email protected]

Vite picks these up automatically and bakes them into the bundle.

Run the dev server

npm run dev

The admin runs at http://localhost:5173. Open it in your browser, log in with your bootstrap admin email + password.

The first time you run the admin (locally OR in production), you'll need to fill in the Flexweg API key + site URL under Settings → General. These live in Firestore (config/flexweg), so once saved they apply to both your local dev session AND any production deploy of the same Firebase project.

What npm run dev does

  1. Prebuild Tailwind themes — runs scripts/build-theme-tailwind.mjs which compiles magazine and corporate themes' Tailwind CSS into theme.compiled.css (since they use a Tailwind preprocessor, not Vite's PostCSS). Default theme uses SCSS, handled separately.
  2. Vite dev server — HMR on. Editing any .tsx / .ts file rebuilds the changed module and pushes it to the browser without reload.

In dev mode, plugins and themes live as static imports in src/plugins/index.ts and src/themes/index.ts. They're bundled into the main admin chunk, so HMR works for them too. The runtime external loader (which fetches Firestore for the registry) is bypassed in dev — import.meta.env.DEV evaluates to true and the BUILTINS_DEV array populates PLUGINS / THEMES directly.

Run tests

npm test # one-shot
npm run test:watch # watch mode

The test suite is Vitest. Tests live next to the code (*.test.ts, *.test.tsx). 100+ unit tests cover slug rules, plugin registry, block registry, image processing, menu resolver, etc.

Type-check

npm run typecheck

Runs tsc --noEmit against the entire codebase. The build pipeline runs this automatically (prebuild script), so a npm run build that succeeds means typecheck passed.

Build for production

npm run build

This runs:

  1. npm run typecheck — guarantees the build is type-clean
  2. npm run themes:tailwind — compile Tailwind themes
  3. vite build — produce dist/admin/ (the admin SPA)
  4. node scripts/build-themes.mjs — copy compiled theme CSS into dist/theme-assets/
  5. node scripts/build-bundled-externals.mjs — emit each in-tree plugin / theme as a separate ESM bundle under dist/admin/{plugins,themes}/<id>/

Output structure:

dist/
├── admin/ ← upload this to /admin/ on Flexweg
│ ├── index.html
│ ├── config.js ← Firebase config (null stub if no .env)
│ ├── external.default.json ← bundled plugins/themes baseline
│ ├── assets/ ← admin bundle
│ ├── runtime/ ← runtime stubs for external bundles
│ ├── plugins/<id>/bundle.js ← per-plugin ESM bundle
│ └── themes/<id>/bundle.js + theme.css
└── theme-assets/ ← upload this to /theme-assets/ on Flexweg
└── <theme-id>.css ← public-facing theme CSS

Daily dev workflow

  1. npm run dev keeps running in a terminal.
  2. Edit code, the browser reloads.
  3. Before committing, run npm test && npm run typecheck to catch regressions.
  4. To deploy: npm run build, upload dist/admin/ and dist/theme-assets/ to your Flexweg site.

Adding a new in-tree plugin

  1. Create src/plugins/<plugin-id>/.
  2. Add manifest.ts (or manifest.tsx) that default-exports a PluginManifest object. Use @flexweg/cms-runtime imports, NOT relative paths to admin internals.
  3. Append the import + array entry in src/plugins/index.ts.
  4. Append the metadata to PLUGINS in scripts/build-bundled-externals.mjs so the build script knows to compile it.
// scripts/build-bundled-externals.mjs
const PLUGINS = [
{ id: "core-seo", name: "Core SEO", version: "1.0.0" },
{ id: "your-plugin", name: "Your Plugin", version: "1.0.0" }, // ← add
// …
];
  1. Run npm run dev — HMR picks up your changes immediately.
  2. When ready: npm run build produces a separate bundle for your plugin under dist/admin/plugins/<plugin-id>/. It'll be listed in external.default.json automatically.

See [Creating plugins] for the full tutorial.

Adding a new in-tree theme

Same idea: src/themes/<theme-id>/, manifest at the root, append to src/themes/index.ts and the THEMES array in scripts/build-bundled-externals.mjs. See [Creating themes].

File watching gotchas

  • Vite HMR watches src/. Changes to public/, index.html, vite.config.ts or package.json require a server restart.
  • Tailwind compilation is not part of HMR for in-tree themes. If you edit src/themes/magazine/theme.css, run npm run themes:tailwind manually OR run npx tailwindcss -c <config> -i <input> -o <output> --watch in a second terminal — Vite picks up the rewritten output via its file watcher.
  • The runtime stubs in public/runtime/*.js are static files served as-is. Editing them requires a manual reload.

Continue