Files
2025-10-02 19:27:15 -07:00

6.2 KiB

Build Instructions

Development

Setup

# Install dependencies
pnpm install

# Copy environment file
cp .env.example .env

# Edit .env as needed
# For local development with parent backend:
# PUBLIC_API_URL=http://localhost:3000
# PUBLIC_SEASON=s1
# PUBLIC_ENABLE_TURNSTILE=false

Run Dev Server

pnpm dev

App runs on http://localhost:5173

Note: Dev server proxies /api/* to http://localhost:3000 (see vite.config.ts)


Production Build

Build for Parent Backend

The build is configured to output directly to the parent backend's frontend/ folder:

pnpm build

Output location: ../frontend/ (replaces existing compiled frontend)

What Gets Built

../frontend/
├── _app/                    # SvelteKit app chunks
│   ├── immutable/
│   │   ├── assets/         # CSS files
│   │   ├── chunks/         # JS chunks
│   │   ├── nodes/          # Route components
│   │   └── entry/          # App entry points
│   └── version.json
├── index.html              # Main page
├── 404.html               # 404 fallback
├── admin/
│   └── index.html
├── moderation/
│   └── index.html
├── join/
│   └── index.html
└── ... (other static files)

Backend Integration

The parent backend (in ../src/index.ts) serves the built frontend:

// Serve frontend static files
app.use('/', sirv('./frontend', {
  maxAge: 31536000, // 1 year for immutable files
  immutable: true
}));

// SPA fallback - serve 404.html for unmatched routes
app.use((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.sendFile(path.join(__dirname, '../frontend/404.html'));
});

All API routes (/me, /auth/*, /alliance/*, etc.) are handled by the backend first, then unmatched routes serve the frontend.


Environment Variables

Build Time (PUBLIC_*)

These are embedded into the built JavaScript:

  • PUBLIC_API_URL - API base URL (default: '' = same origin)
  • PUBLIC_SEASON - Current season (default: 's1')
  • PUBLIC_ENABLE_TURNSTILE - Enable Cloudflare Turnstile (default: 'false')
  • PUBLIC_TURNSTILE_SITE_KEY - Turnstile site key

Development vs Production

Development (.env):

PUBLIC_API_URL=http://localhost:3000
PUBLIC_SEASON=s1
PUBLIC_ENABLE_TURNSTILE=false

Production (backend serves frontend from same origin):

PUBLIC_API_URL=
PUBLIC_SEASON=s1
PUBLIC_ENABLE_TURNSTILE=true
PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F

Turnstile Configuration

Disable Turnstile (Development)

PUBLIC_ENABLE_TURNSTILE=false
  • No Turnstile widget rendered
  • Login buttons work immediately
  • OAuth URLs don't include token parameter
  • Backend should handle auth without Turnstile

Enable Turnstile (Production)

PUBLIC_ENABLE_TURNSTILE=true
PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
  • Turnstile widget loads on login page
  • Login buttons disabled until captcha completes
  • OAuth URLs include ?token=... parameter
  • Backend validates Turnstile token

Build Process

Clean Build

# Remove old build
rm -rf ../frontend/_app ../frontend/index.html

# Fresh build
pnpm build

Verify Build

After building, check the parent backend:

cd ..
pnpm start

# Open browser to http://localhost:3000
# Should see the new frontend

Backend Requirements

The parent backend must:

  1. Serve static files from ./frontend directory
  2. Handle API routes first (before static file serving)
  3. Serve 404.html for SPA fallback on unmatched routes
  4. Set appropriate headers (CORS, CSP, etc.)

Example setup in ../src/index.ts:

import sirv from 'sirv';
import path from 'path';

// API routes first
app.get('/me', authMiddleware, ...);
app.post('/auth/logout', ...);
// ... all other API routes

// Serve frontend static files
app.use('/', sirv('./frontend', {
  maxAge: 31536000,
  immutable: true
}));

// SPA fallback
app.use((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.sendFile(path.join(__dirname, '../frontend/404.html'));
});

Troubleshooting

Build fails with "Cannot find module"

rm -rf node_modules
pnpm install

Changes not appearing

# Clear browser cache
# Or use incognito mode

API calls return 404

  • Check PUBLIC_API_URL in .env
  • For production, set to empty string (same origin)
  • Backend API routes must be registered before static file serving

Turnstile not working

  • Check PUBLIC_ENABLE_TURNSTILE is 'true' (string, not boolean)
  • Verify PUBLIC_TURNSTILE_SITE_KEY is correct
  • Check browser console for errors

CI/CD Integration

Example GitHub Actions

name: Build Frontend

on:
  push:
    branches: [main]
    paths:
      - 'frontend-src/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 10
      - uses: actions/setup-node@v3
        with:
          node-version: 24
          cache: 'pnpm'
          cache-dependency-path: frontend-src/pnpm-lock.yaml

      - name: Install dependencies
        run: cd frontend-src && pnpm install

      - name: Build
        run: cd frontend-src && pnpm build
        env:
          PUBLIC_API_URL: ''
          PUBLIC_SEASON: 's1'
          PUBLIC_ENABLE_TURNSTILE: 'true'
          PUBLIC_TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}

      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: frontend-build
          path: frontend/

File Structure Reference

FurryPlace/
├── frontend/               # ← Build output (served by backend)
│   ├── _app/
│   ├── index.html
│   └── ...
├── frontend-src/           # ← Source code (this directory)
│   ├── src/
│   ├── static/
│   ├── package.json
│   ├── svelte.config.js   # Output to ../frontend
│   └── BUILD.md           # This file
└── src/                   # Backend source
    └── index.ts           # Serves ./frontend