Product & governance
Share the content. Keep control of each website.
It started with…
Several websites share parts of their offer and content. Copying pages between them makes every correction harder to follow.
So we built…
A page model linked to websites: each site has its domains, theme and publishing scope. Shared pages retain explicit links to where they appear.
- Supabase
Shared content
Pages and versions
- Supabase
Publishing scope
Page × website links
- Vercel
Render by domain
Website theme and navigation
Simplified diagram of the reviewed model.
What we set out to change
Reuse content while making publishing decisions explicit for each website.
How it is builtData model, code, controls and team ownership
The user journey
- 01
Create or enrich the shared page.
- 02
Preview it in each website’s context.
- 03
Publish the approved version to selected websites.
Data and hand-off structure
| Entity / table | Key fields | Relationships |
|---|---|---|
| Websites & domains | Host · theme · scope | One site → several domains |
| Pages | Content blocks · version · status | One page → several site links |
| Page/site links | Site · page · local slug · published | Explicit publication per website |
Technical implementation
- The host resolves the current website; a junction table links its pages.
- Preview snapshots and published versions are distinct.
- Components share a data contract while allowing presentation choices per website.
Engineering decision
Publish one page selectively across several sites
Adapted from the public branch of the multi-site page resolver. Both the site/page association and the page status must allow publication; SEO overrides apply to that association. The database client uses public read policies. Preview and admin access are deliberately separate and are not exposed through a visitor-controlled boolean in this excerpt.
import "server-only";import { createPublicClient } from "./database";import { parsePageContent } from "./content-schema";export async function resolvePublicPage(siteId: string, localSlug: string) { const db = await createPublicClient(); const { data: link, error } = await db.from("page_site_links") .select(` local_slug, is_published, seo_overrides, pages!inner(id, title, content, seo, status) `) .eq("web_site_id", siteId) .eq("local_slug", localSlug) .maybeSingle(); if (error) throw new Error("Page lookup failed"); if (!link?.is_published) return null; const page = link.pages; if (!page || page.status !== "prod") return null; return { id: page.id, title: page.title, content: parsePageContent(page.content), seo: { ...(page.seo ?? {}), ...(link.seo_overrides ?? {}) }, };}// siteId is resolved from the trusted hostname/site mapping.// Public row policies and a unique (web_site_id, local_slug) are required.Adapted implementation excerpt
Controls and boundaries
- Scope checks are needed on every read and write.
- Private previews must protect both content and media.
- Code rollback, editorial versions and data restoration need separate plans.
Who can contribute
- Communications: content and publishing.
- Administrators: websites, domains and access.
- Developers: components, migrations and scope checks.
What supports this case
Multi-site model, publishing relationships and preview flow reviewed in the repository. This describes the implementation, not a completed security audit.