Skip to main content

All Souls Seattle

All Souls is an Anglican Church in West Seattle, part of Churches for the Sake of Others (C4SO) in the Anglican Church In North America (ACNA).

All Souls Seattle

I am a member at All Souls Seattle, and in late 2025 I began collaborating with their staff to rebuild the organization’s website. The old site ran on Craft CMS and was hosted on Bluehost. It worked, but the technology palette was fairly outdated, the code was not being tracked using git / version control, and every update depended on someone who understood the CMS which significantly slowed down change. It was also costing the organization upwards of $400 a year. For a small staff whose real work is pastoral care, worship, and community, the website was quietly taking time it shouldn’t. Beyond a refreshed design, we agreed on six jobs for the project:

  1. Migrate off Bluehost and Craft CMS.
  2. Reduce what the church spends on its website.
  3. Improve security and performance, including faster asset loading through a CDN.
  4. Make the church easier to find, through both SEO and AEO.
  5. Make the site more autonomous, so staff spend less time maintaining it.
  6. Modernize the stack so it can connect to tools like Slack, PostHog, and MCP.

Every decision below traces back to one of those six jobs.

Demo

Right Arrow Icon View the live site

Tech Stack

  • TypeScript
  • Next.js (App Router)
  • React
  • Tailwind CSS
  • Sanity
  • pnpm and Turborepo
  • Vercel
  • Cloudflare
  • Storybook
  • PostHog

Architecture

Two architectural decisions shaped everything else: how the code is organized, so it stays maintainable by whoever comes next, and how content gets from the CMS to visitors, so publishing is fast without sacrificing page speed.

A Monorepo Organized by Concern

I structured the project as a monorepo. The public site is a Next.js app on Vercel, the CMS is Sanity Studio, and the shared pieces: the UI library, the Planning Center and podcast integrations, and the book metadata lookups. Each of these shared pieces live in their own package in the monorepo. The reason is maintenance. Whoever works on this next, whether that is me in two years or another volunteer, should be able to find each concern in one place and change it without unraveling everything else.

Publishing Without a Deploy

Publishing new content or updating existing content never requires a new deploy. When staff publish a document in Sanity, the website is told which pages changed and refreshes only those. Visitors get pages that are fast because they are cached, and staff still see their edits go live within seconds. Getting both at once, static-site speed without a rebuild on every edit, is the main reason I chose this stack over another traditional/server-rendered CMS. Sanity CMS is also headless, meaning we can use the content elsewhere if needed. If the organization wants to develop a mobile app in the future, for example, we can use the same content if desired and updates would propagate to both clients. Finally, it’s possible to schedule changes in Sanity CMS which will help if anything needs to be published or unpublished on a specific date/time.

Cost

Cost was a goal in its own right, because every dollar the church spends on hosting is a dollar it is not spending on people.

  • CMS – I applied for and secured Sanity’s nonprofit plan for the organization. This allows the organization to effectively get Sanity’s growth plan for free, only paying for additional usage beyond the plan limits.
  • Code – The code now lives tracked under version control in GitHub at no cost.
  • Hosting – Vercel’s free tier covers the site today. We will eventually move the project into a Vercel team so access does not depend on my personal account. That could add a small cost, but it’s a governance decision rather than a scaling one and it would still be well below the old hosting cost.
  • Domain – There was a slight cost reduction for the domain, because I helped migrate the domain from GoDaddy to Cloudflare. Cloudflare sells domains at wholesale (at-cost) pricing with zero markup, no hidden fees, and no inflated renewal prices.

Security and Performance

Without an IT department, security has to come primarily built in to the design rather than from someone watching the server logs 24/7. In other words, the church does not have a team dedicated to have an on-call rotation!

The approach for security was to shrink the surface area of what could go wrong. Fewer dependencies to patch, credentials kept where they belong, and assets delivered from the edge so the site stays fast.

Separating the CMS, Site, and Hosting

On the old site, the CMS, the server, and the public pages all lived together on one shared host, so a problem in any one of them put the whole site at risk. The new architecture separates those concerns. Sanity hosts the CMS, Vercel serves the public site, and there is no server of our own left to patch or babysit. That alone removes a large category of risk for a church without a dedicated IT team.

Keeping Credentials Out of the CMS

I also followed a simple principle with every integration. The CMS should never hold the keys to other systems. Credentials for services like Planning Center Online stay on the server side of the website, and the public site only shows what each page has been set up to show. The browser is never trusted to decide what it is allowed to see. Keys are also scoped, whenever possible, to grant access only for what actions and resources are needed.

Storing and Rotating Keys

I was deliberate about where every key lives. Anything that has to stay private, like the tokens for writing to the CMS, reading from Planning Center Online, or running the podcast import, is stored as an encrypted secret in the system that uses it. GitHub Actions secrets for scheduled jobs and deploys, and sensitive environment variables in Vercel for the website. Those values are never committed to the repo, never sent to the browser, and cannot be read back once they are saved. Anything the browser genuinely needs, like the public project ID for the CMS or the PostHog project key, is stored as a plain variable instead. Those values end up in the page on the client side no matter what, so treating them as secrets would only hide them from the people maintaining the site without protecting anything.

Keeping that line clear is also what makes rotation routine. Replacing a key means updating it in the one place it is stored and redeploying, with no code changes. Because each secret is scoped to the system that needs it, rotating one does not break the others, and a leaked key can be revoked and replaced quickly.

Serving Assets Through a CDN

For performance, images and files are served from the church’s own domain through Cloudflare, so they are cached close to visitors and the URLs stay consistent with the rest of the site. Doing it this way also meant we did not need an enterprise Sanity plan just to serve assets from our own domain.

One Source of Truth

The biggest time cost on the old site was not design. It was double entry. Staff would add an event to Planning Center and then add it again to the website, or publish a sermon to the podcast and then copy it into the CMS. Every duplicated step was extra work and another chance for the two to drift apart. So the rule for the rebuild was simple: each kind of content has one home, and the website reads from it.

Events from Planning Center

Events live in Planning Center, where staff already manage them. Calendar blocks on the website only store which calendars and tags to show, and they fetch upcoming events from the Planning Center API at request time. Responses are cached on the server for five minutes, which keeps pages fast and avoids calling Planning Center on every page view, while still meaning an event edited in Planning Center shows up on the website within minutes, without anyone opening the CMS.

Sermons from Buzzsprout

Sermons start in Buzzsprout, the church’s podcast host. A GitHub Actions workflow runs on a daily cron schedule, reads the podcast feed, and pulls new episodes into the CMS as unpublished drafts. It can also be triggered manually when staff do not want to wait for the next run. The import is idempotent: each episode maps to a stable document ID, so running the job once or ten times produces the same result, and a retried or doubled run never creates duplicates. Landing as drafts gives staff a chance to review before anything goes public. If they want to attach the original audio file later, the Studio compresses it in the browser before uploading, because sermon recordings are large and I did not want staff to have to learn audio tools to post one.

Books from an ISBN

Books follow the same idea. Staff enter an ISBN, and the Studio looks up the title, author, and cover from Amazon’s product API, falling back to Open Library when Amazon has no match. From that ISBN, the website then builds the buy-or-borrow links for each book: Amazon, Audible, Goodreads, and Libby for borrowing through the public library.

Automatic Redirects

The same thinking applies to URLs. When a page’s URL changes, a redirect is created automatically, so old links in newsletters, bulletins, and search results keep working without anyone having to remember to set one up.

Editing Without a Developer

Autonomy also means staff can build new pages without me. The page builder is made of nested layouts and blocks, so a ministry page can take its own shape without a developer creating a new template for it. Editing happens inside a live preview of the real site, so what staff see while they work is exactly what visitors will see. I spent real time making that preview reliable across browsers, including Safari, because an editor that only works in one browser is not one a staff team can depend on.

Discoverability: SEO and AEO

A church website is often someone’s first contact with the community, so being found matters. People now find churches two ways: through traditional search results, and by asking AI assistants directly. The site is built for both.

Search Engine Optimization

Every page has proper metadata, social preview images are generated automatically, and the site publishes a sitemap. I also added structured data that describes the church, its staff, and its articles, with the address and contact details coming from a single settings document so they never disagree from page to page.

Answer Engine Optimization

That structured data is the heart of the AEO work. More and more people ask an AI assistant or answer engine something like “Anglican church in West Seattle” instead of scrolling through search results, and those tools rely on clear, machine-readable facts to answer well. Stable URLs, protected by the automatic redirects, mean that whatever they cite keeps working.

Integrations and Instrumentation

Part of the reason for leaving Craft was that every new integration felt like its own project. On this stack, connecting a tool is usually a webhook or a small package, which made it practical to wire up alerting, analytics, and internal documentation from the start.

Alerts in Slack

Vercel posts deploy notifications to Slack, and PostHog posts site performance alerts there too, so problems surface where the team already talks instead of in a dashboard nobody checks.

Analytics as a Contract

For analytics, I treated events as a contract rather than an afterthought. Every event the site sends to PostHog has a versioned schema in the repo, and I built a small documentation site, analytics-docs, on a subdomain of the church’s domain, that publishes those definitions. Anyone wondering what an event means can look it up instead of reading code, and changing an event becomes a deliberate, reviewable decision rather than an accident.

Internal Docs Behind Zero Trust

Those docs describe how the site is instrumented, which is useful to staff but not something the public needs to see, so I put the subdomain behind Cloudflare Zero Trust. Staff have to verify their identity before Cloudflare lets a request through, so access control lives at the edge instead of in the app, and there is no separate login system for me to build or maintain. Down the road, I may move internal tools like this onto their own internal-only domain. Applying Zero Trust to that whole domain would protect every current and future subdomain by default, so a new internal tool could never accidentally go live in public, and it would keep public and internal concerns fully separate.

Branding and Design

Branding was a partnership with staff, and the colors and fonts stayed the same. What I added underneath was a shared component library with Storybook, so every block can be viewed and reviewed on its own. That keeps the site consistent as it grows, and it makes it easier for the next person to contribute without breaking a page they did not know existed. Storybook is a natural first tenant for the internal-only domain I mentioned above: it is a tool for staff and contributors rather than visitors, so it would sit behind the same Zero Trust policy as the analytics docs with no extra setup.

What’s Next

This is still a living project. Two directions are on the roadmap: one is planned and next in line, and the other is still an idea that depends on decisions with staff.

Building Pages with Claude

The next piece is MCP support for the page builder. MCP, the Model Context Protocol, is a standard way for AI assistants like Claude to use outside tools, and Sanity provides an MCP server for its content. Because every page on the site is already assembled from structured blocks, connecting that server would let staff open Claude, describe the page they need, such as a landing page for a new class with a schedule, a short description, and a sign-up link, and have it assemble the page from the same blocks they would otherwise arrange by hand. That turns a half hour of clicking into a conversation, and it builds on the same goal as the rest of the project: staff should be able to do more on their own without waiting on a developer.

Guardrails for AI Edits

Giving an AI assistant write access to a live website is also a real risk, so the guardrails matter as much as the feature. The plan is to give it permissions narrow enough that it simply cannot do damage: it can create and edit drafts, but it cannot delete documents or publish anything. A person still reviews every change in the Studio and decides whether it goes live. Everything it writes has to pass the same schema validation as a human edit, so it can only use blocks and fields that already exist, and the CMS keeps full revision history, so any change can be rolled back. The aim is for Claude to be a fast assistant inside the existing workflow, not a new way to break the site.

Listening Progress Across Devices

The latest stretch of work turned sermons into something closer to a listening experience than a list of embeds. The site remembers where a visitor left off and marks what they have finished. Today that progress lives in the browser’s local storage, which needs no login and keeps listening history on the visitor’s device, but it only follows them within that one browser. Switch from a phone to a laptop, and the progress does not come along.

Making progress sync across devices means storing it somewhere other than the browser, so one idea I am considering is a lightweight account: visitors would sign in with Google or another identity provider, and their progress would be tied to that sign-in rather than to a device. Relying on an existing provider avoids the church ever storing passwords. The same accounts could later support a member directory, but that raises real questions about privacy, consent, and who can see whom, which are decisions for staff and not something to settle in code. Because it needs that collaboration, it is still just an idea, and I have deliberately not prioritized it yet.