From Jekyll to SvelteKit: How We Used AI and TDD to Rewrite This Site
If you’re reading this on the live site, you can’t tell what happened. That’s the point.
The pages load. The nav works. The blog posts are all there. The floating booking button still follows you around.
But underneath, everything changed.
This site was originally built on Jekyll -a Ruby-based static site generator that served me well. It had layouts, Liquid templates, markdown blog posts, YAML front matter, and a build pipeline held together by a Gemfile.
It also had 202 inline style attributes, scattered across 9 content files, 3 layouts, and 12 blog posts. Every page duplicated the same GA4 snippet, the same favicon block, the same Termly consent script. It worked, but it wasn’t going to scale.
So I rewrote it. Here’s how.
The Goal
I wanted four things:
- No regression - every feature, every link, every meta tag had to survive intact
- Component architecture - no more copy-paste HTML, no more 202 inline styles
- Fast builds - the SvelteKit static adapter gives me sub-second rebuilds on content changes
- A developer experience that scales - Svelte components, scoped CSS, file-based routing
The Strategy: TDD on an Existing Codebase
Most rewrites fail because you lose track of what the old site actually did. You think you’ve covered everything, and then someone finds the footer link that now 404s.
I avoided this with a TDD-first migration:
Step 1: Write Playwright tests against the live Jekyll build
Before touching a single line of new code, I installed Playwright and wrote 9 test suites covering:
- Routes - every URL returns the right status code
- Content - every heading, section, and CTA exists
- SEO - every page has the right title, description, OG tags, and schema
- Links - every internal link resolves, every anchor targets a real element
- Layout - nav, footer, floating button, hamburger, exit-intent modal
- Blog - all 13 posts render, have dates, hero images, and prev/next navigation
- Assets - favicons, fonts, CSS, JS, PDFs all load
- Scripts - hamburger toggles, exit modal fires, nav shrinks on scroll
- Case Studies - all 3 case studies have the right structure
I ran them against the Jekyll build. All green. A regression harness, built in an afternoon.
Step 2: Scaffold SvelteKit with the same test target
I set up SvelteKit with @sveltejs/adapter-static - it generates flat HTML, CSS, and JS files, just like Jekyll does. The same nginx Dockerfile works unchanged.
Then I updated the Playwright web server to serve the SvelteKit build instead.
All tests turned red. Now I had a clear finish line: implement until they’re all green again.
Step 3: Migrate file by file, component by component
This is where AI came in.
How AI Changed the Workflow
I used OpenCode - an agentic CLI tool - to orchestrate the migration. Here’s what that looked like in practice.
Phase 1: Exploratory audit
I asked an agent to explore the entire codebase and give me a structured report. It found:
- All 9 content pages and their dependencies
- The 202 inline styles, indexed by file and line
- The Liquid template logic that needed replacing
- The exact set of static assets (favicons, PDFs, images)
- The 12 blog posts and their front matter schema
A human could do this, but it would take half a day of clicking through files. An agent did it in under two minutes.
Phase 2: Planning
Based on the audit, I designed a 7-phase migration plan:
- Layout shell -
+layout.sveltewith nav, footer, analytics, Termly, exit modal - Shared components - Nav, Footer, Hero, ROICard, ServiceTier, PostPager, etc.
- Core pages - Home, About, Services, Case Studies
- Blog system - Markdown processing with mdsvex, blog listing + individual posts
- Utilities - Calculator, Privacy Policy, Cookie Policy, 404
- SEO & plugins - sitemap.xml, RSS feed, OG tags
- Inline style elimination - 202 inline styles → scoped Svelte components
Phase 3: Delegated execution
Instead of writing every file myself, I gave each phase to a dedicated agent:
“Create
src/lib/components/Nav.svelte. It needs a fixed dark nav bar with logo, hamburger toggle, nav links matching the existing site. Port the JS logic for hamburger toggle + close-on-outside-click. Use scoped styles. Zero inline styles.”
The agent wrote the component, I reviewed it, and it went into the build.
This pattern repeated for every component, every page, every route. I was an editor and architect, not a line-by-line coder.
Phase 4: Continuous regression testing
After each batch of changes, I re-ran the Playwright suite:
npx playwright test --reporter=list The test suite became the source of truth. If a test passed, the feature was correct. If it failed, I knew exactly what needed fixing - no manual QA, no clicking through every page.
What Migrated
| Component | Before (Jekyll) | After (SvelteKit) |
|---|---|---|
| Pages | 9 content pages + 2 standalone HTML files | 13 Svelte routes |
| Blog posts | 12 markdown files with YAML front matter | 13 markdown files processed by mdsvex |
| Layouts | 3 Liquid templates with inline styles | 1 +layout.svelte + |
| CSS | 1 global file, 1549 lines | 1 global file + per-component scoped CSS |
| Inline styles | 202 across all files | 0 |
| JS | 1 script (103 lines, DOM manipulation) | Split into component onMount blocks |
| Build | jekyll build (Ruby, ~8s) | npm run build (Node, ~45s) |
| Deploy | nginx Docker, _site/ output | nginx Docker, build/ output |
What I Learned
AI works best with a harness
Without the Playwright test suite, I wouldn’t trust the AI-generated code. With it, I could delegate liberally and verify aggressively. The AI wrote the components; the tests confirmed they worked.
Context windows are the real bottleneck
The AI couldn’t see the entire codebase at once. I had to break the work into small, self-contained chunks - each one small enough to fit in a prompt, each one producing a test-verifiable output. This is good discipline anyway.
The design didn’t change
This was a pure engineering migration. The visual design, typography, colour palette - all unchanged. The CSS file was ported as-is. Users see exactly what they saw before. That’s the whole point of TDD: you can prove nothing broke.
Static sites still win
SvelteKit + adapter-static produces the same kind of output as Jekyll - flat HTML and CSS. No server, no database, no CDN complexity. You still get the DX of a modern framework (hot reload, SPA-like navigation, component architecture) without the operational cost.
The Result
The test suite is still green.
Every page renders. Every blog post renders. The exit-intent modal fires once per session. The floating booking button follows you down the page.
But the codebase is now:
- Componentised - no duplication, no copy-paste HTML
- Type-safe - SvelteKit catches routing errors at build time
- Fast to build - sub-second incremental rebuilds
- Tested - 9 suites, dozens of assertions, zero tolerance for regression
- Future-proof - Svelte is a thriving ecosystem, not a legacy toolchain
And the migration itself is now repeatable. The same Playwright test suite that validated this rewrite can validate the next one - whether that’s a design overhaul, a content restructure, or a framework change.
Want to do the same for your site?
If you have an older static site that’s getting hard to maintain, the approach scales:
- Write the regression tests first - one afternoon of test writing saves weeks of manual QA
- Use AI for the grunt work - component scaffolding, style porting, route creation
- Let the test suite drive - red means “not done yet,” green means “ship it”
I’ve written up the full Playwright test structure and migration plan in a reusable template that you can use as a starting point for your own migration.
Ship without fear
AI-assisted development is only as good as your regression harness. Let's build one together - I'll show you how to test-drive your next migration.
Book a Strategy Call