Skip to content

birosrichard/blog-prototype

Repository files navigation

blog-prototype

Standalone prototype for the blog migration off Ghost. This repo is deliberately separate from the main web app so the architectural decisions can be evaluated in isolation, without dragging in the rest of the website's deps.

The shape of the prototype is Approach C from the migration discussion:

  • Content in this repo (content/blog/*.md)
  • Rendered by Next.js (App Router, RSC, statically generated)
  • A writer UI overlay at /admin (Decap CMS)
  • Agents read/edit posts the same way they edit code: filesystem + PRs

Stack

Matches the main web app baseline:

  • Next.js ^16.2.4 (App Router, RSC, generateStaticParams)
  • React 19.2.3
  • TypeScript ^5.9.3 (strict)
  • styled-components ^6.1.19 (with the SWC plugin)
  • Node 24.14.1 (.nvmrc)

Plus the blog-specific bits:

  • gray-matter — frontmatter parsing
  • zod — frontmatter validation, so a malformed post can't crash the build
  • react-markdown + remark-gfm — body rendering (no MDX yet — see below)
  • decap-cms — writer UI, self-hosted bundle (no CDN dependency)

Running it

nvm use
npm install
npm run dev

Then:

For local content editing through the admin UI without a GitHub round-trip, see docs/decap-local-dev.md.

Layout

blog-prototype/
├── content/
│   └── blog/
│       ├── README.md                     # Frontmatter spec
│       ├── welcome-to-the-new-blog.md    # Published
│       ├── why-we-moved-off-ghost.md     # Published
│       └── agents-as-content-contributors.md  # Draft (hidden from index)
├── public/
│   └── admin/
│       ├── config.yml                    # Decap config
│       └── decap-cms.js                  # Bundle (copied from node_modules at install)
├── scripts/
│   └── copy-decap-bundle.mjs             # postinstall hook
├── src/
│   ├── app/
│   │   ├── layout.tsx                    # Root layout: header, footer, styled-components SSR
│   │   ├── page.tsx                      # Home (latest 3 posts)
│   │   ├── admin/
│   │   │   └── route.ts                  # /admin — boots Decap, bypasses root layout
│   │   └── blog/
│   │       ├── page.tsx                  # /blog — list of all published posts
│   │       └── [slug]/
│   │           └── page.tsx              # /blog/<slug> — generateStaticParams
│   ├── lib/
│   │   ├── blog/
│   │   │   └── posts.ts                  # gray-matter + zod reader
│   │   └── styled/
│   │       └── StyledRegistry.tsx        # styled-components SSR registry
│   └── ui/
│       ├── theme/theme.ts                # Local design tokens
│       ├── components/GlobalStyles.tsx
│       ├── layout/SiteHeader.tsx
│       ├── layout/SiteFooter.tsx
│       └── blocks/
│           ├── Home/HomePageContent.tsx
│           └── Blog/
│               ├── BlogIndexPageContent.tsx
│               ├── BlogPostListItem.tsx
│               └── BlogPostPageContent.tsx
├── docs/
│   └── decap-local-dev.md
├── next.config.ts                        # CSP, styled-components SWC plugin
├── tsconfig.json                         # Strict TS, path aliases
└── package.json

What this proves vs. the prior in-repo prototype

The prior prototype lived inside the main web repo and was meant to show that the prototype could plug into the existing app. This one tests a different question: what does the codebase look like if the blog is its own thing?

Specifically, this prototype shows:

  1. A clean stack baseline. No shared design system package, no external CMS client, no Sentry, no GraphQL codegen, no React Compiler config — just Next.js + styled-components + Decap. About 18 files of source code total.
  2. Self-hosted Decap bundle. The earlier prototype loaded decap-cms.js from unpkg.com, which forced a CSP carve-out. Here the bundle is copied from node_modules to public/admin/ by a postinstall script, so the same-origin CSP works unmodified.
  3. A real frontmatter pipeline. gray-matter + Zod validation, so the index doesn't crash if someone commits a post with a missing title: field.
  4. An explicit cms-config-url link. Avoids the bug where Decap resolves config relative to the URL and asks for /config.yml at the site root.

What's still stubbed

This is a prototype. Things that would need to be addressed before this ships as the production blog:

  • OAuth. Without a GitHub OAuth proxy, the admin UI loads but writers can't sign in. See docs/decap-local-dev.md for the local-only flow and the production options.
  • MDX or Markdoc. Plain markdown gives no path to embed React components inside posts. The decision between MDX, Markdoc, and a tagged schema is the next meaningful design call.
  • Image handling. No next/image integration for cover images yet. Decap config writes paths to public/images/blog/* but nothing renders them.
  • SEO. Basic OpenGraph metadata only. Production needs OG image generation, JSON-LD article schema, and an RSS feed.
  • Redirects. A real migration needs a redirect map for legacy URLs from the old blog domain to the new one.
  • Tests. A real implementation would have unit tests for the file reader and a Cypress (or Playwright) test for the rendered pages.
  • CI / hosting. No deploy pipeline yet. The point is to evaluate the shape, not the deploy.

How it relates to the bigger decision

This is one of three architectural shapes the team is weighing:

  • A — Content inside the main web repo. Cheapest to build, mixes blog churn into the main repo's history.
  • B — Content in a sibling repo (this prototype's layout). Clean separation; needs a build-time fetch from the main web repo, OR sites stay separate.
  • C — Content in GitHub + writer UI overlay. The CMS overlay angle. This prototype demonstrates B + C.

The prototype answers concrete questions about how the GitHub-as-source- of-truth approach would feel, both for engineers and for writers. It doesn't answer whether that's the right call vs. a Notion-driven path.

blog-prototype

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors