eng-supabase-prerender-blog-pipeline
Rating is derived from the repo's GitHub stars and shown for reference.
name: eng-supabase-prerender-blog-pipeline
description: Use when building or maintaining the static pre-rendering pipeline that generates SEO-optimized HTML from blog posts and legal insights stored in Supabase. Covers the build trigger, Supabase storage integration, edge-caching strategy, and OG-metadata generation for a legal AI content marketing site.
license: MIT
metadata:
id: eng.supabase-prerender-blog-pipeline
category: eng
jurisdictions: [multi]
priority: P2
intent: [eng, supabase, blog, prerender, seo, static-site]
related: [eng-supabase-edge-functions-patterns, eng-remotion-explainer-video-generator, eng-supabase-index-knowledge-pipeline]
source: Louis — HAQQ Legal AI (github.com/sboghossian/mini-claude-for-legal)
version: "1.0"
Supabase Prerender Blog Pipeline
What it does
The prerender blog pipeline converts blog posts and legal insight articles (stored as rows in Supabase) into pre-rendered static HTML files that are served from Cloudflare (or Supabase Storage with CDN). This matters for SEO: legal AI content ("UAE employment contract template", "DIFC vs ADGM for fintech") has significant organic search value, but client-side-only React apps are not indexed reliably by all crawlers. Pre-rendering solves this without requiring a full SSR infrastructure.
Setup / auth
Supabase schema:
CREATE TABLE blog_posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
body_md TEXT NOT NULL, -- Markdown source
body_html TEXT, -- Pre-rendered HTML (populated by pipeline)
excerpt TEXT,
cover_image_url TEXT,
author_id UUID REFERENCES auth.users(id),
jurisdiction TEXT[], -- e.g., ["UAE", "DIFC"]
practice_area TEXT[],
published BOOLEAN NOT NULL DEFAULT FALSE,
published_at TIMESTAMPTZ,
og_title TEXT,
og_description TEXT,
og_image_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
No auth required for public blog reads. The pipeline runs with the service role key (it writes body_html).
Capabilities
Pipeline trigger
The pipeline runs on:
blog_postsrow insert/update (Postgres webhook → Edge Functionprerender-post).- Nightly cron (rebuild all published posts, catches dependency updates like layout changes).
- On-demand via
POST /api/admin/prerender(admin only).
Rendering steps
- Fetch post — load
slug,title,body_md, metadata from Supabase. - Parse Markdown — use
markedorunifiedwith legal-specific plugins:- Auto-link jurisdiction names to relevant resource pages.
- Highlight
[DISCLAIMER]blocks with a styled callout component. - Convert
[[skill-id]]wikilinks to actual/skills/{id}URLs.
- Generate OG metadata — if
og_titleis null, derive fromtitle; ifog_descriptionis null, use first 160 chars ofexcerpt. OG image: use a template (Vercel OG, Satori, or a Remotion still frame — see [[eng-remotion-explainer-video-generator]]). - Inject into HTML template — hydrate a minimal HTML shell with
body_html, head meta tags, and JSON-LD structured data (LegalArticleschema type). - Write to storage — upload rendered HTML to Supabase Storage bucket
blog-html/{slug}/index.html. - Purge CDN cache — call Cloudflare Cache Purge API for the slug URL.
- Update row — set
body_htmlandupdated_atin the database.
JSON-LD structured data (SEO)
{
"@context": "https://schema.org",
"@type": "LegalArticle",
"headline": "{{title}}",
"author": { "@type": "Organization", "name": "Louis Legal AI" },
"datePublished": "{{published_at}}",
"about": [
{ "@type": "Thing", "name": "{{jurisdiction[0]}}" }
],
"description": "{{og_description}}"
}
Using LegalArticle type (instead of generic Article) improves Google's classification of legal content.
Arabic / RTL blog posts
- Arabic posts set
lang="ar" dir="rtl"on the<html>element. - The HTML template must include an Arabic web font (IBM Plex Arabic) in the
<head>. Prerender time is not critical, so full font loading is acceptable. - Slug for Arabic posts: use the Latin transliteration in the URL (
/ar/aqd-amal-lubnan), not Arabic Unicode, for URL portability.
Multilingual routing
| Path | Language |
|---|---|
/blog/{slug} |
English (default) |
/ar/blog/{slug} |
Arabic |
/fr/blog/{slug} |
French |
Each language variant is a separate row in blog_posts with a lang column and a translation_of FK pointing to the canonical English post.
Usage patterns
Triggering a single post prerender
const res = await supabase.functions.invoke("prerender-post", {
body: { slug: "uae-employment-law-2024" },
});
Listing posts by jurisdiction for sitemap
const { data } = await supabase
.from("blog_posts")
.select("slug, updated_at, jurisdiction")
.eq("published", true)
.order("published_at", { ascending: false });
Permissions & safety
- The
prerender-postfunction runs with service role to writebody_html. It must not be callable by unauthenticated requests. - Never render a post with
published = falseto the public-facing storage bucket. - Markdown-to-HTML pipeline must sanitize HTML (
DOMPurifyorsanitize-html) to prevent stored XSS from user-authored content. - Do not include user-submitted content (comments, intake form data) in pre-rendered HTML — that would be a persistent XSS vector.
Failure modes
| Failure | Impact | Mitigation |
|---|---|---|
| Markdown parse error | body_html not updated; old content served |
Catch parse errors; set post status to render_error; alert |
| Storage upload fails | CDN serves stale content | Retry 3× with backoff; after 3 failures, alert on-call |
| Cloudflare purge fails | Stale HTML cached | CDN max-age should be 1 hour max; purge failure is recoverable |
| OG image missing | Social previews are blank | Always generate a default OG image if og_image_url is null |
Arabic post served without dir="rtl" |
Broken layout | Validate HTML output includes dir="rtl" for Arabic posts before uploading |
Related skills
- [[eng-supabase-edge-functions-patterns]] — the Edge Function pattern used by
prerender-post - [[eng-remotion-explainer-video-generator]] — generates OG/social video cards for high-value posts
- [[eng-supabase-index-knowledge-pipeline]] — similar pipeline for indexing documents (compare patterns)