Open, practical documentation

Build Your Own Church Website

Circuit Rider is intentionally built from ordinary, documented tools. Churches and volunteers are welcome to study the system, reproduce it, and make it fully their own.

A route you can follow

These guides explain what each part does, why it exists, and how the parts work together. You do not need to adopt every service or stay hosted by Circuit Rider. The goal is understanding and independence, not dependence on a platform.

Some steps are architectural overviews because exact, repeatable setup instructions are still being documented. We say so plainly rather than offering commands that have not been tested.

The complete route

Start with the map. The Linux server hosts each built static website, and Nginx serves those files. Cloudflare Tunnel connects a public hostname to that server. In the dynamic church-site architecture described here, Vite and React provide the frontend, while a Cloudflare Worker handles dynamic requests. D1 stores structured content, R2 stores uploaded documents, and Cloudflare Access protects administration.

How requests move

  • Public pageVisitor → Cloudflare → Tunnel → Nginx → React site
  • Dynamic contentReact site → /api/* → Worker → D1
  • DocumentsReact site → Worker → D1 metadata + R2 file storage
  • AdministrationAdministrator → Cloudflare Access → /admin/* → React admin → Worker

Why separate the pieces?

Each part has one understandable responsibility. Nginx is excellent at serving files; the Worker handles the small amount of application logic; D1 stores searchable facts; R2 holds larger files; and Access handles identity before an administrative request reaches the application.

This separation keeps ordinary pages fast and available without running an application server for every visit. It also gives a church replaceable parts: the static site is not locked to the database or file-storage provider.

Circuit Rider currently uses Linux for the web server. For a small church website, a modest always-on computer can be sufficient; capacity depends on the number of sites, traffic, backups, and other work assigned to the machine.

Before beginning, you need administrative access to a supported Linux installation, a reliable network connection, a backup destination, and someone responsible for operating system updates and recovery.

What the server is responsible for

  • Keeping each church in its own repository, Unix account, document root, and backup
  • Building the source into disposable static output
  • Letting Nginx read the deployed output without giving the web server write access
  • Receiving Tunnel traffic without making the whole machine a shared church application

Nginx is the public site's file server. It receives a request passed through the Tunnel, finds the matching church's deployed document root, and returns HTML, CSS, JavaScript, images, and other static assets.

A complete setup will cover

  • Installing Nginx from the Linux distribution
  • Creating a separate server block and document root for each church
  • Serving only generated production files
  • Setting useful security and caching headers
  • Validating configuration before reload
  • Testing the site and a rollback path

The dynamic church-site design uses Vite and React for its frontend. Authors work in a source project containing components, styles, content integration, and configuration. Vite creates an optimized production build. Deployment copies those built static files into the document root that Nginx serves.

Keep these three things distinct

  • Source project: the editable, version-controlled files used by maintainers
  • Production build: generated HTML, CSS, JavaScript, and assets created from the source
  • Deployed static files: a copy of that build in the server location read by Nginx

Generated output is disposable. It must never be the only copy of the website's content or configuration.

Cloudflare Tunnel creates an outbound connection from the Linux server to Cloudflare. A public hostname can then route through that connection to Nginx, so the server does not need to accept unsolicited inbound internet traffic on a public web port.

Tunnel is transport, not the website itself. The church should still own its domain, understand its DNS, keep its source and backups, and document how the hostname maps to the appropriate Nginx site.

D1 is the structured database in the current dynamic-content design. Events and announcements live as records rather than being hard-coded into a page. The current model is shared and multi-tenant: every church has a church_id, and every tenant-owned record is associated with it.

The Worker determines tenant identity from the request hostname. The browser must never be allowed to choose its own church_id; accepting that value from a form, URL, or request body would let a client ask to act as another tenant.

Store meaning; generate presentation.

The durable schema should record what something means: title, description, dates, status, church ownership, and other facts. Page layout, CSS classes, preformatted HTML, and display-specific labels belong in presentation code. That keeps the same data useful in a website, calendar, email, export, or a future design.

Tenant rules

  • Resolve the church from a trusted hostname mapping
  • Associate every tenant-owned row with church_id
  • Validate fields before writing
  • Scope reads, changes, and deletions to the resolved church

The Worker handles /api/* requests. Ordinary website traffic continues through the Tunnel to Nginx. The React site calls same-origin relative URLs such as /api/events, which avoids unnecessary cross-origin request and CORS complexity.

For every request, the Worker resolves the church from the hostname and talks to D1 through an environment binding. Operations on an existing row must be scoped by both the trusted church identity and the record ID—not by record ID alone.

Existing-record operation = resolved church + record ID.

A shared Worker can serve multiple churches because routing and application rules are reusable while tenant identity and data remain explicit. This reduces duplicated API code without allowing one church to select or modify another church's records.

Administrative pages live under /admin/*. Cloudflare Access enforces authentication before that traffic reaches the application. Hiding an admin link is not security: an attacker can still request a known or guessed URL.

Administrative APIs must be protected as carefully as the interface that calls them. The current design places both the React admin UI and /admin/api/* under the same Access application, so the browser cannot bypass authentication by calling a differently routed management endpoint.

Protection boundary

  • /admin/* contains the administrative interface
  • /admin/api/* contains administrative operations
  • Access policy is enforced before both reach the application
  • The application still validates input and scopes every data operation

Event: something people can attend or participate in at a particular date or time.

Event examples

  • Retreat
  • Potluck
  • Special worship service
  • Meeting
  • Community activity

Announcement: information people need to know.

Announcement examples

  • Cancellation
  • Weather notice
  • Schedule change
  • Church office closure
  • Volunteer request

This distinction lets the site display and expire information appropriately. Ordinary recurring activities—weekly worship, study, choir, or office hours—can stay on a stable Weekly Schedule page instead of creating an endless series of database events. Use an event when a particular occurrence needs its own timely record.

D1 = what the document is

R2 = the actual document

PDFs are stored privately in R2. D1 stores the metadata needed to understand and find each document.

Document metadata

  • Church
  • Bulletin or newsletter type
  • Title
  • Publication date
  • Storage key
  • Status

Public downloads pass through the Worker instead of exposing raw R2 object keys. The Worker finds an allowed metadata record for the resolved church and returns the corresponding file.

Because the website asks for a document by its public meaning rather than by a storage-provider URL, R2 can be replaced later without changing the public website concept. A migration would move files and update the storage adapter while preserving stable public routes and metadata.

Git records project history locally. A commit is an intentional save point with a message explaining the change. Git does not automatically commit every edit.

The basic rhythm

  • Edit and review the project files
  • git add stages the changes intended for the next save point
  • git commit creates that save point
  • Inspect history and restore known versions when needed

A Git repository works locally without GitHub. A remote host and off-machine backups can be added later, and the church should control those accounts. Some Circuit Rider projects use a small commit.sh convenience script to make the review, staging, and commit rhythm easier; it does not change what Git is doing underneath.

Independence is the long-term destination. A technically capable church should be able to take its source, content, original media, documentation, exports, and credentials and deploy a complete system under accounts it owns.

An independent deployment can include

  • Its own website and source repository
  • Its own Worker
  • Its own D1 database
  • Its own R2 bucket
  • Its own Cloudflare Access setup
  • Its own domain, DNS, credentials, and backups

A church does not need to remain hosted by Circuit Rider. Handoff must include tested build, deployment, backup, recovery, and export instructions—not merely a copy of the generated website. Cloudflare services can also be replaced when the church chooses equivalent hosting, API, database, document-storage, and authentication responsibilities.

Ownership means the practical ability to operate, move, recover, and change the system.

We build it. We teach it. You own it.

— The Circuit Rider promise