| .forgejo/workflows | ||
| .storybook | ||
| infra | ||
| public | ||
| scripts | ||
| sites | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .eslintrc.cjs | ||
| .gitignore | ||
| .prettierrc.json | ||
| .yarnrc.yml | ||
| _template.scss | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| next.config.mjs | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Parish Website Template
A white-label website template for Catholic parishes. One codebase powers multiple parish sites; the active site is selected at build/run time via the NEXT_PUBLIC_SITE_ID environment variable, and each site's branding (name, colors, fonts, logo, favicon, contact info) lives under sites/<siteId>/. The repository currently ships with dreikoenige (Heilige Drei Könige) and chemnitz as reference deployments.
Built with:
- Next.js 15 (App Router)
- React 19
- Payload CMS v3
- PostgreSQL with PostGIS
Quick start
- Requirements
- Node.js 22+ (see
enginesinpackage.json) - npm (or your preferred package manager)
- Database PostgreSQL with PostGIS extension
- Install dependencies
npm install
- Configure environment
cp .env.example .env
Then update the values as needed. Example keys from .env.example:
DATABASE_URI— database connection string (Mongo example provided). For Postgres, use a URL likepostgres://user:password@host:5432/dbname.PAYLOAD_SECRET— secret used by PayloadGOOGLE_BUCKET— GCS bucket name (if using @payloadcms/storage-gcs)RESEND_API_KEY— API key for Resend (emails)
- Run the development server
npm run dev
By default the app runs at http://localhost:3000
Database options
PostgreSQL + PostGIS
You can run a Postgres image with PostGIS locally:
docker pull postgis/postgis
Create and run a container, then set your DATABASE_URI to the Postgres URL. Ensure the PostGIS extension is enabled in your database.
Alternatively, you can enable the Postgres service in docker-compose.yml (currently commented out) and use that as your local DB.
Google Cloud Storage
Media and document uploads use @payloadcms/storage-gcs. The plugin is always registered but conditionally enabled via the GOOGLE_BUCKET environment variable:
GOOGLE_BUCKETset — files are uploaded to GCS and served fromhttps://storage.googleapis.com/<bucket>/.GOOGLE_BUCKETunset — the plugin is disabled and Payload falls back to local file storage. ThealwaysInsertFieldsflag keeps the schema identical in both cases, sopayload-types.tsandimportMap.jsstay stable across environments.
No additional configuration is needed for local development — simply omit GOOGLE_BUCKET from your .env.
Migrations (Payload)
Create a new migration:
npm run payload migrate:create --<name>
Apply pending migrations:
npm run payload migrate
Note: After deploying changes to production, ensure migrations are executed against the production database. (Todo: Integrate this step into Continuous Deployment.)
Admin panel (Payload)
Once the server is running, open:
Recurring mass times (jobs queue)
Churches have a recurringSchedule field on their admin page where editors can
configure wiederkehrende Gottesdienste — weekly, biweekly (with an anchor date
for the 2-week parity), or monthly by weekday (e.g. every 3rd Sunday). These
entries are not rendered directly: a background job materializes them into real
Worship documents for a rolling window a few weeks into the future, so the
existing MassTimesBlock, gemeinde pages and /gottesdienst/{id} detail pages
work unchanged. Generated documents are normal Worship docs — an editor can
still cancel a specific occurrence, change the celebrant, etc., and the job
will never overwrite manual edits (it only ever appends new rows).
The task is defined in src/jobs/generateRecurringMasses.ts and is triggered
three ways:
- On save —
Churches.afterChangequeues a job whenever a schedule is edited. - Weekly cron — the task carries its own
scheduleso Payload auto-queues it every Monday at 03:00 to keep the rolling window populated. - Manually — queue from code with
payload.jobs.queue({ task: 'generateRecurringMasses' }), or run the full queue from the CLI:npm run payload jobs:run --cron "* * * * *" --queue default
autoRun in payload.config.ts only fires in production
(NODE_ENV === 'production'), so in development you either need the CLI
command above, flip shouldAutoRun to true temporarily, or hit the "Run now"
button on the Payload Jobs admin page.
Note: Payload's
autoRunrequires a long-running server. This project ships via Docker, so that's fine — but if the app ever moves to a serverless host like Vercel,autoRunmust be replaced with an external cron (e.g. Vercel Cron) that invokes the CLI command above.
Scripts
Common scripts available in package.json:
npm run dev— start Next.js dev servernpm run build— build for productionnpm start— start the production servernpm test— run tests (Vitest)npm run storybook— start Storybook on port 6006
Storybook
Storybook lets you develop and preview UI components in isolation.
Start Storybook:
npm run storybook
Site Metadata
Site-wide metadata (title, description, keywords, OpenGraph) is configured in src/config/site.ts. Update that file to change SEO defaults used in the root layout.
License
This software is distributed under a commercial license, not as open source. Purchasing a license grants the right to deploy and modify the Software on servers you operate; redistribution, resale, and use as a hosted service for third parties are not permitted. See LICENSE for the full terms, or contact Benno Tielen Benno@tielen.nl for licensing inquiries.
A note on updating dependencies
Payload CMS and Next.js are pinned to specific versions. From the Payload docs we currently have the following requirements (March 2026):
Next.js (one of the following version ranges): 15.2.9 - 15.2.x 15.3.9 - 15.3.x 15.4.11 - 15.4.x 16.2.0-canary.10+