EmDash CMS Case Study: Building EmDashKits

EmDash CMS Case Study: Building EmDashKits

Most CMS "customer success stories" are curated testimonials from companies with a vested interest in looking good. This is something more useful and more honest: a real account of what running EmDash actually looked like on this site — including the parts that broke, needed a second pass, or took longer than expected — not a highlight reel.

Table of Contents
  1. The Starting Point
  2. A Live Database Migration, Under Real Constraints
  3. A Feature That Doesn't Exist Yet, Worked Around Honestly
  4. Structured Data and Accessibility Fixes
  5. A Third-Party Integration That Needed a Platform Patch
  6. A CSS Bug Specific to How EmDash Renders Comments
  7. What This Actually Demonstrates
  8. Frequently Asked Questions
  9. Why a first-party case study instead of third-party customer testimonials?
  10. Does the Postgres search limitation apply to every EmDash deployment?
  11. Is patching EmDash's CSP via patch-package a supported pattern, or a hack?
  12. The Bottom Line

The Starting Point

This site runs on EmDash with an Astro front end, originally deployed on libSQL (Turso). Content is structured — posts, taxonomies, bylines, comments — with the public-facing pages built as standard Astro pages querying EmDash's content API directly.

A Live Database Migration, Under Real Constraints

At one point this project needed to move from Turso/libSQL to a self-hosted PostgreSQL instance — not a greenfield choice, a real production cutover with existing content, comments, and user accounts already live. That meant using EmDash's dialect-agnostic migration runner and schema registry (the same code paths its own admin API uses) to replicate the schema on Postgres, then copying roughly 47 tables of real data across and verifying row counts matched before cutting over.

It wasn't frictionless. The production cutover initially failed twice — first pointing at an internal-only hostname unreachable from the actual hosting provider's network, then a port-routing conflict on the database host that needed isolating from inside the container to diagnose, then a case-sensitivity issue in the database username that took a runtime error log to catch. None of these were EmDash issues specifically — they were the ordinary friction of a real infrastructure migration — but they're the honest version of "customer success," not a frictionless highlight.

Read also:

A Feature That Doesn't Exist Yet, Worked Around Honestly

EmDash's built-in full-text search is SQLite/libSQL-specific and doesn't support PostgreSQL in the version this project runs. Rather than blocking the migration on that gap, the fix was a small dialect-aware branch in the search page: Postgres deployments fall back to a hand-built ILIKE-based search with manual result highlighting, while SQLite/libSQL deployments keep using EmDash's native full-text search. That's a real, current limitation worth knowing if you're planning a Postgres deployment with search as a requirement — and a workable path around it in the meantime.

Structured Data and Accessibility Fixes

Using EmDash's `page:metadata` hook pattern, this site added Article and Person JSON-LD structured data across posts and author pages, and BreadcrumbList schema on category and tag archive pages. Separately, an accessibility audit caught a genuine bug: a comment-form honeypot field (an intentionally hidden anti-spam trap) was missing `aria-hidden`, which a screen reader would have exposed to real users as an unlabeled, confusing form field. That's the kind of real, specific issue that shows up in production use, not in a demo.

A Third-Party Integration That Needed a Platform Patch

Adding Google Analytics 4 surfaced a real architectural constraint: EmDash sets its own Content Security Policy via middleware, with no exposed configuration option to extend it for a third-party script's required domains. The actual fix was a `patch-package` patch to EmDash's CSP-building function, adding `googletagmanager.com` and the Google Analytics domains to the policy — a legitimate, documented pattern for extending an open-source dependency's behavior in a specific spot without forking the whole project, but a real extra step this specific integration required.

A CSS Bug Specific to How EmDash Renders Comments

A comment section's avatar images were rendering as a full-width bar instead of a small circle — the root cause was that Astro's scoped-style mechanism doesn't apply to elements created via client-side JavaScript template strings (which is how EmDash's comment list renders dynamically). The fix was wrapping the relevant CSS selectors in `:global(...)`, a specific, learnable pattern for anyone building custom styling around EmDash's dynamically-rendered UI pieces.

What This Actually Demonstrates

  • A real production database migration is achievable using EmDash's own documented tooling (schema registry, migration runner), though it still requires the same infrastructure diligence any database migration does.
  • Feature gaps (Postgres full-text search) are real but workable around without blocking a launch.
  • Extending platform behavior EmDash doesn't expose configuration for (like CSP) is possible via patch-package, a standard open-source pattern, not a dead end.
  • Real bugs — an accessibility miss, a CSS scoping surprise — show up in production use the way they do on any platform, and EmDash's documented architecture (hooks, Astro integration patterns) made each one a specific, fixable problem rather than a mystery.

Frequently Asked Questions

Why a first-party case study instead of third-party customer testimonials?

Because it's verifiable and specific, rather than a curated quote that could be true of any platform. This account includes the actual friction points, not just the parts that make EmDash look good.

Does the Postgres search limitation apply to every EmDash deployment?

As of the version this project runs, yes — built-in full-text search is SQLite/libSQL-specific. Check current EmDash documentation before a new Postgres deployment if native search matters to your project, since this is exactly the kind of thing that could change in a future release.

Is patching EmDash's CSP via patch-package a supported pattern, or a hack?

It's a legitimate, standard open-source technique for extending a dependency's behavior in a specific, scoped way without forking the whole project — not something EmDash discourages, just something worth knowing you might need for certain third-party integrations.

The Bottom Line

Running EmDash in production on this site involved real migration friction, a genuine current feature gap, a platform-behavior extension via a standard patching pattern, and ordinary bugs — all fixable using EmDash's own documented architecture. That's a more useful signal than a polished testimonial: the platform held up under real, messy production conditions, not just a clean demo. See our full EmDash CMS review for the broader verdict this experience feeds into.

Share

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Fix
  4. Lessons learned

Diagnostic

Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.

Root cause

// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com

A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.

Read also:

Fix

// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com

Lessons learned

  • Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
  • "Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
  • Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
Share
Previous Article

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Comments

Write a comment

Related Articles

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Lessons learned

Diagnostic

Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.

Root cause

The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.

// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
emdashkits.com
Read also:

Lessons learned

  • "No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
  • Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Share
Previous Article

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Next Article

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)