Frontend for the Goteo crowdfunding platform — built on Astro 7 + Svelte 5 + TailwindCSS 4, deployed on Cloudflare Workers.
NOTE: This application is a client of the Goteo v4 API. You need a running v4 API instance and an OAuth client registered in it before this web app will work.
This application requires Node.js (v22.12 or later) and the pnpm package manager. It also needs a reachable Goteo v4 API instance to connect to.
git clone https://github.com/goteo/org.goteo.www
cd org.goteo.wwwpnpm installCopy the example environment file and fill in the values for your setup.
cp .env.example .envThe minimum required values to boot the app and authenticate users are:
| Variable | Description |
|---|---|
PUBLIC_API_URL |
Base URL of your Goteo v4 API instance (e.g. http://localhost:8090) |
PUBLIC_API_VERSION |
API version — keep as v4 |
OAUTH2_CLIENT_ID / OAUTH2_CLIENT_SECRET |
Credentials of an OAuth client registered in the API. Must allow the grants authorization_code, refresh_token, password |
NOTE: The OAuth client must exist in the v4 API instance. See the API's Authentication docs.
The remaining variables (tipping, Facebook share, object storage, HTTP Basic auth) are optional and the app falls back to sane defaults when they are empty. See .env.example for the full annotated list and the Environment variables section below.
Start the development server.
pnpm devThe app should be live at http://localhost:4321.
The dev server runs on the Cloudflare Workers runtime (workerd), the same one used in production, with bindings read from wrangler.toml. No separate command is needed to reproduce the production runtime locally.
| Command | Action |
|---|---|
pnpm install |
Install dependencies |
pnpm dev |
Dev server at localhost:4321 (Workers runtime) |
pnpm build |
Build for production |
pnpm preview |
Preview production build locally |
pnpm format |
ESLint fix + Prettier write |
pnpm check |
Prettier format check |
pnpm check:astro |
Type check .astro files (astro check) |
pnpm check:svelte |
Type check .svelte files (svelte-check) |
pnpm db:migrate |
Apply D1 migrations to the local database |
pnpm db:seed |
Seed the local D1 database |
pnpm sdk |
Regenerate OpenAPI SDK from live API spec |
pnpm storybook |
Storybook dev server at localhost:6006 |
pnpm build-storybook |
Build Storybook static output |
The TypeScript API client under src/openapi/client/ is generated from the live API's OpenAPI spec. Regenerate it whenever the API spec changes (requires the API running and PUBLIC_API_URL / PUBLIC_API_VERSION set):
pnpm sdkThis fetches the spec from $PUBLIC_API_URL/$PUBLIC_API_VERSION/docs.json. Commit the generated files together with any config change.
Component and unit tests run on Vitest through the Storybook test addon. There is no pnpm test script; run them with:
pnpm exec vitestLaunch Storybook to develop and visually check components:
pnpm storybookpnpm format # Fix formatting (ESLint + Prettier)
pnpm check:astro # Type check .astro files
pnpm check:svelte # Type check .svelte filesCommon issues you might run into while developing:
This almost always means the app cannot reach the API or the OAuth client is misconfigured. Check that:
PUBLIC_API_URLpoints to a reachable v4 API instance and includes the protocol (http:///https://).- An OAuth client exists in that API instance and its
OAUTH2_CLIENT_ID/OAUTH2_CLIENT_SECRETmatch your.env. - The OAuth client allows the
authorization_code,refresh_tokenandpasswordgrants.
The generated SDK is out of date. Regenerate it (API must be running):
pnpm sdkThe dev server and production both run on the Cloudflare Workers runtime, which has no Node.js APIs. Runtime code (pages, components, services) must use Web APIs (fetch, crypto, URL, Cache) instead. Move any Node-only logic to build-time code (config files, scripts, plugins), where it is still allowed.
| Layer | Technology |
|---|---|
| Framework | Astro 7 |
| UI | Svelte 5 (runes), TailwindCSS 4 |
| Language | TypeScript 5 |
| Runtime | Cloudflare Workers (also supports Node) |
| API client | @hey-api/client-fetch (generated OpenAPI) |
| Testing | Vitest, Storybook |
All user-facing pages live under src/pages/[...locale]/ (locale prefix: es, en, ca).
| Route | Page |
|---|---|
/ |
Home |
/search |
Project search |
/project/[idOrSlug] |
Project detail |
/project/[idOrSlug]/edit |
Project editor (auth) |
/create/project |
Create project (auth) |
/checkout |
Checkout cart |
/checkout/payment |
Payment |
/checkout/post-payment |
Post-payment confirmation |
/checkout/verify |
Payment verification |
/checkout/wallet |
Wallet confirmation |
/login |
Login |
/login/callback |
OAuth2 callback |
/logout |
Logout |
/register |
Register |
/me |
My profile (auth) |
/me/manage |
Manage my projects (auth) |
/user/[idOrHandle] |
Public user profile |
/about |
About |
/static/[fileName] |
CMS static content |
/admin |
Admin dashboard (admin) |
/admin/charges |
Admin contributions (admin) |
/admin/projects |
Admin projects (admin) |
/admin/users |
Admin users (admin) |
/admin/home |
Home hero & highlights (admin) |
/admin/comm |
Banners & newsletter (admin) |
/403, /404, /500 |
Error pages |
API endpoints under src/pages/api/:
| Endpoint | Purpose |
|---|---|
/api/relay/[...path] |
Authenticated API proxy |
/api/session |
Read session (GET), password login (POST) |
/api/upload/preupload |
S3 pre-signed URL generation |
/api/upload/postupload |
S3 post-upload validation |
/api/upload/delete |
Delete an uploaded object |
src/
├── actions/ # Astro server actions (form mutations)
├── auth/ # JWT sessions, OAuth2 tokens, refresh logic
├── components/ # Svelte components (PascalCase filenames)
│ ├── library/ # Reusable UI primitives + Storybook stories
│ ├── admin/ # Admin-specific components
│ ├── icons/ # Icon SVG components
│ └── [feature]/ # Feature-scoped (project/, profile/, checkout/, etc.)
├── firewall/ # Route access control rules
├── i18n/ # Translations (es/en/ca) + t() store
├── layouts/ # Astro layout components
├── middleware/ # Astro request middleware (auth, locale, firewall)
├── openapi/ # OpenAPI SDK (generated) + config
├── pages/ # Astro routes
│ ├── [...locale]/ # All user-facing pages under locale prefix
│ └── api/ # Server-side API endpoints
├── repositories/ # Persistence: drafts (Dexie/IndexedDB), banners, highlights, home hero (D1)
├── services/ # Business logic & API call wrappers
├── stores/ # Svelte stores (client-side state)
├── styles/ # Global CSS + Tailwind theme tokens
├── types/ # Shared TypeScript interfaces
├── utils/ # Helper functions (checkouts/, media/ subfolders)
└── validation/ # Project/reward/collab/register validators
Full list lives in .env.example. Grouped by purpose:
| Variable | Required | Purpose |
|---|---|---|
PUBLIC_API_URL |
✅ | Base URL of the v4 API instance |
PUBLIC_API_VERSION |
✅ | API version (v4) |
OAUTH2_CLIENT_ID / OAUTH2_CLIENT_SECRET |
✅ | OAuth client credentials registered in the API |
PUBLIC_DEFAULT_CURRENCY |
Fallback currency (e.g. EUR) |
|
PUBLIC_DEFAULT_LANGUAGE |
Fallback locale (es, en, ca) |
|
PUBLIC_DEFAULT_COUNTRY |
Fallback ISO 3166-1 alpha-2 country (default ES), e.g. preselected tax id country |
|
PUBLIC_DEFAULT_MAXSIZE |
Max upload size in bytes (default 8388608 = 8MB) |
|
PUBLIC_TIPPING_TIPJAR_ID |
Tipjar ID — leave empty to disable tipping | |
PUBLIC_TIPPING_DEFAULT_AMOUNT |
Pre-filled tip amount | |
PUBLIC_TIPPING_DEFAULT_CHECKED |
"true" to pre-check the tip option |
|
PUBLIC_FACEBOOK_APP_ID |
Facebook share dialog app ID | |
CLOUDFLARE_INCLUDE_PROCESS_ENV |
"true" — required for Cloudflare Workers to read env vars |
|
BASIC_AUTH |
"true" to add an HTTP Basic auth layer (not a replacement for OAuth) |
|
BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD |
Credentials for the Basic auth layer | |
OBJECT_STORAGE_* |
S3-compatible storage (access key, secret, region, endpoint, bucket) for media upload |
| Workflow | Trigger | Action |
|---|---|---|
prettier-check.yml |
PR to main / develop |
Prettier format check |
crowdin-upload.yml |
Push to develop touching es.json, manual |
Upload source strings to Crowdin |