The blog you're reading this on started life as a Day 71 project from the 100 Days of Python course (that I actually coded). It was a functional Flask blog with Bootstrap styling, CKEditor for rich text, SQLite for storage, and a basic auth system. It worked, but it looked very much like a course project.
I wanted to bring it into the same design system as the portfolio site at jamescarty.co.uk so that it felt like part of the same ecosystem, not a bolted-on afterthought.
What changed
The reskin touched nearly everything in the front-end. Bootstrap was removed entirely and replaced with standalone CSS. The colour palette shifted to dark backgrounds with mint accents, the typography changed to Raleway for headings and Source Sans Pro for body text, and a dark mode toggle was added using [data-theme="dark"] CSS selectors with localStorage persistence.
The layout was rebuilt from scratch: a clean header with navigation, card-based post listings on the index page, and a focused single-column reading layout for individual posts. The admin pages (create post, edit post, login, register) were restyled to match. Every template was rewritten.
I did the reskin using an iterative mockup approach with Claude before touching any code. We went through several rounds of side-by-side comparisons, adjusting spacing, typography, and colour balance until the design felt right. Only then did the CSS and templates get updated. That process of designing before coding saved a lot of rework.
The platform move
The original project ran locally with SQLite. For production I needed a hosted database and a deployment platform. I chose Vercel for hosting and Neon PostgreSQL for the database, which introduced a few platform-specific challenges.
Vercel's serverless environment has a read-only filesystem. Flask expects to write to an instance directory, so I had to set app.instance_path = '/tmp/instance' to redirect writes to the only writable location. Static files needed to route through Flask rather than using a separate Vercel static route, because the two approaches conflicted.
The Neon PostgreSQL connection string required removing channel_binding=require from the URL, which psycopg2 doesn't support. That one took a while to diagnose; the error messages weren't particularly helpful.
CKEditor
The course project used CKEditor 4. I upgraded to CKEditor 5 (v41 Classic build) for the post editor, but hit a licensing issue: versions 44 and above require a paid licence. So v41 it stays.
I also added a custom "HTML Source" toggle button that lets me switch between the visual editor and raw HTML input. This is how the blog diagrams work: I create SVG content separately, switch to HTML Source mode, paste it in, and submit. The textarea bypasses CKEditor on submit so the raw HTML goes straight to the database. The CKEditor field also needed required=False in the WTForms definition to prevent HTML5 validation from blocking submission when the textarea is active instead.
Diagram support
The blog posts include inline SVG diagrams (like the architecture diagrams in earlier posts). These need to render correctly in both light and dark mode, so the diagram styles use CSS custom properties that respond to the [data-theme="dark"] selector. The diagram theme guide defines the palette: blue for infrastructure, teal for application services, cool gray for neutral elements. All diagrams follow the same visual language across every post.
Getting the SVGs to render properly inside CKEditor's output required some CSS specificity work in the blog's stylesheet, but once the styles were in place it's been reliable.
What I'd flag for anyone doing similar
A few things that weren't obvious: Vercel's read-only filesystem catches you if you're not expecting it; set the instance path early. Neon's connection strings include parameters that not all Python database drivers support; test the connection before building anything on top of it. CKEditor 5's licensing changed significantly after v41; check before upgrading. And if you're reskinning a course project, do the design work first with mockups rather than iterating in code; it's faster and the result is better.
The blog is live at projects.jamescarty.co.uk and the source is on GitHub.
Comments 0
Log in or register to comment.