The problem we were solving
The Compass dev team has been in an absolute tear, building websites for the community and events. Each site cloned from a previous project and applying improvements as we go. The Detroit Pride Summit is one of the latest sites to be built, based on the International Women’s Day AI Summit website. This gives us a technical advantage, but also presents a challenge in that we also have the cloned history of its predecessors. We aim to change that by building a more flexible and maintainable system.
Conference websites look simple from the outside. They show a speakers grid, a schedule, and session cards with headshots. Under the hood, our previous site looked like many event sites: a massive speakers.js file with hundreds of lines of duplicated data.
Each row failed to represent a unique speaker. Instead, a row represented a person plus a single session appearance. A panelist appearing three times had their bio and photo copied three times. A separate allowlist decided who appeared on the site. Some headshots lived in the repository, while others pointed at Cloudinary URLs from an older platform. Broken links meant broken photos, and non-developers had no way to fix them.
The 2026 Detroit Pride Innovation Summit required a better system. We needed:
Fresh content for this event, without pasting last year’s roster forward.
A workflow organizers already understand: Google Sheets and a shared Drive folder for photos.
A stable content source for the website: Sanity CMS.
Updates that bypass React file edits and manual redeploys during panelist swaps.
Here is the architecture of the new system.
The Big Picture
The pipeline uses four distinct layers. Each layer has one specific job:
flowchart TD
accTitle: The content pipeline, from spreadsheet to live site
accDescr: A Google Sheet and a Google Drive folder feed an n8n workflow, which publishes to Sanity CMS. The Vite/React site fetches from Sanity at build time and deploys statically on Vercel.
sheet["Google Sheet<br> runner’s master list"]
drive["Google Drive<br> renamed headshots: FirstnameLastname.jpg"]
n8n["n8n workflow<br> import on demand"]
sanity["Sanity CMS<br> published content + images on Sanity’s CDN"]
site["Vite/React site<br> fetches at build time → static deploy on Vercel"]
sheet --> n8n
drive --> n8n
n8n --> sanity
sanity --> site
Caption: Architecture diagram mapping the data flow. Source data starts at a Google Sheet master list and a Google Drive image folder. Both sources feed into an n8n automation workflow. The workflow processes the data and pushes updates to Sanity CMS. At build time, the Vite/React site fetches content from Sanity and generates a static deployment on Vercel.
Organizers plan in Sheets. The live site trusts Sanity. Then, n8n connects the two tools during a sync.
We avoid piping the spreadsheet directly to the website. Direct piping introduces fragile URLs, bypasses validation, and removes the ability to safely preview or unpublish a speaker.
Content model: people, sessions, and events
Instead of managing one giant array with duplicate person records, we model three distinct document types in Sanity Studio. The TypeScript schema lives in a /studio folder next to the React application:
Event
One document represents each summit year (e.g., Detroit Pride Innovation Summit 2026). This document anchors all data for that year. It will power archive pages for past speakers within the same Sanity project.
Speaker
One document represents a single person per event. It contains the name, bio, organization, title, headshot, LinkedIn URL, and community badges. One photo and one bio exist per person, regardless of their session count.
Session
One document represents a single talk or panel slot. It tracks the title, track, room, Eastern Standard Time, duration, and description. Sessions link to speakers through a participants reference list. A panel requires one session document with multiple speaker references, eliminating duplicate bios.
Tracks and room names remain free text. Venues and lineups change annually; avoiding rigid enums prevents system breakages when the run-of-show evolves.
How the Google Sheet fits in
We avoid importing raw form submissions. The event runner assembles a master sheet after filtering proposals, assigning tracks, conducting community voting, ordering sessions, and confirming speakers.
Only confirmed speakers land on the master sheet. If a speaker is missing from the sheet, they do not appear on the site.
Each row represents one person in one session. For panels, every panelist receives a row with the identical session title. The frontend component groups these rows into a single session card with multiple avatars.
Columns map directly to Sanity fields: speaker slug, session title, track, time, room, bio, organization, badges, and a headshot filename that matches the asset in Drive.
When a speaker drops out, organizers remove them from the sheet or mark them inactive. The next import unpublishes the document in Sanity. Urgent fixes can still occur directly inside Sanity Studio between imports.
Images: Drive in, CDN out
External image URLs in content fields cause broken links. The new workflow enforces strict asset management:
- Speakers submit photos with raw camera filenames.
- Volunteers rename files to a clean pattern: FirstnameLastname.jpg.
- Files live in a dedicated Google Drive folder for the event year.
- During import, n8n downloads the file and uploads it to Sanity as a managed image asset.
- The frontend exclusively uses Sanity’s image CDN to deliver resized WebP thumbnails for sessions and larger assets for speaker modals.
Missing headshots trigger a placeholder graphic until the file arrives in Drive and the import runs.
n8n: the glue
A self-hosted n8n instance uses a Google Cloud service account to access Sheets and run the import workflow. The workflow executes these steps:
- Reads the runner’s master sheet.
- Upserts speakers and sessions in Sanity using idempotent operations for safe, repeated execution.
- Pulls headshots from Drive by filename match.
- Attaches participant references to sessions.
- Unpublishes assets and documents missing from the sheet.
- Triggers a Vercel redeploy webhook to rebuild the static site with fresh content.
- Sends a notification on execution failure.
Imports run on demand instead of using continuous polling. When the programming team completes sheet updates, we trigger the workflow. This matches the reality of event planning, where schedules lock in bursts rather than constant increments.
What the website does
The public site runs as a static Vite/React application. It never calls Google Sheets or n8n at runtime. At build time, it queries Sanity, transforms the data to match our existing component props, and compiles the result into static files.
Caption: A Google Sheet or Drive update is imported by the n8n workflow into Sanity CMS, which triggers a Vercel deploy hook. The static rebuild publishes the change to the live site.
Client-side features like schedule bookmarking use stable session identifiers from Sanity. Saved schedules will reset if the underlying session IDs change, which aligns with normal attendee expectations during event prep.
Speakers grid vs. schedule
The speakers section displays one unique card per person, even if they participate in multiple sessions. The current UI displays the first session found for that speaker. We will add an explicit featured_session field later if we need to override row order.
The schedule section displays every session slot. It merges panelists sharing a session title into a single card featuring multiple headshots, one talk title, and one time slot.
Multi-year management
The architecture uses one Sanity project (pridemi26) with a single production dataset. Speakers and sessions reference an Event document for their specific year. The main site filters queries to the 2026 event doc. Archive routes will query past event documents, maintaining our historical data without requiring new code directories each January.
Speakers represent phase one of this rollout. The same project will eventually store partners, job board listings, team rosters, and activities scoped by event year.
What we deliberately avoided
Hotlinking external production images.
Duplicating speaker biographies across multiple session entries in the CMS.
Requiring editors to modify a JavaScript file via pull requests.
Creating a new Sanity project every year.
Introducing runtime dependencies on Google APIs for page views.
Responsibility matrix
| Role | Tool |
|---|---|
| Event runner | Master Google Sheet, final times/tracks/rooms |
| Volunteers | Rename & upload headshots to Drive |
| Developers | Schema, n8n workflow, site fetch layer, deploy |
| Organizer (later) | Sanity Studio for quick fixes |
| Everyone else | The public site — fast, static, always in sync after a deploy |
Closing
Event websites shouldn’t require a developer every time a panelist changes or a headshot gets swapped. By putting organizers in Sheets and Drive, Sanity in the middle, and n8n on the bridge, we get a pipeline that respects how summit programming actually works — messy, iterative, and deadline-driven — while serving a clean, reliable experience to attendees.
The Pride Innovation Summit site will still look like the site you know. We’re changing what’s under the hood so the team can focus on the program, not on merge conflicts in a 1,400-line JavaScript file.
The next event site will be cloned from this project. This will allow us to expand the system and deliver a robust and flexible solution scheduled for a Hispanic themed event in September. Leading to the year end Detroit Devfest in November. I plan to write an article about the evolution of each site and the lessons learned in my next blog post.
Questions about the stack or want to reuse this pattern for your own event? Contact us
