Back to Blog

From Idea to Live

From Idea to Live

From Idea to Live: Building a Personal Blog from Scratch with Flask and Vanilla CSS

Introduction

A few weeks ago, I decided I wanted a blog where I can share my own writings, product walkthroughs and fun projects which I build on the side apart from learning Physics and wrestling with new programming concepts everyday. Not a WordPress site, Not a Framer Animations loaded site, not a Substack newsletter with little to no followers on any platform — something I built myself, from the ground up, and I have decided to open-source it such that anyone who wants to have a blog like me can get it for minimum friction possible.

I had a few reasons for what I did that follows:

  1. I wanted to understand how web applications actually work.
  2. I wanted to own my content and my design, without being locked into a platform.
  3. I want to handcraft majority of it's portion myself
  4. And I wanted to learn — not by following a tutorial, but by building something real and shipping it and making it visible to the public on a live link.

This is the story of how I built a fully functional blog using Flask, SQLite, and vanilla CSS. No React, no Tailwind, no frameworks. Just Python, a database, and some (1000+ lines <3) handwritten CSS.

It wasn't always pretty. But it taught me more than any course ever could.


The Stack (and Why I Chose It)

The tech stack is simple:

| Layer | Technology | | :---------------------- | :--------------------------------------- | | Backend | Flask (Python) | | Database | SQLite with SQLAlchemy ORM | | Frontend | Vanilla CSS + JavaScript | | Markdown | markdown2 + pygments | | Deployment | PythonAnywhere | | Environment manager | uv and other related tools by Astral |

Why no frameworks?

There are a couple of good reasons:

  1. I wanted to implement every line of code without abstracting away good parts and pain points.
  2. I didn't want a black box that I don't understand myself and even I can't contribute more to moving forward.
  3. I wanted to understand how routing works, how forms work, how form validations work, how authentication works, and how static files are served from a server.
  4. By using Flask and writing my own CSS, I had to understand every part of the stack.
  5. It's a small website, a personal project, an educational project, going pull on AI or Choosing React and Tailwind for the project would definitely be an overkill.

It was slower. It was harder. That's the point. Sometimes it's not always about speed of development but doing something the hardway, reinventing the wheel once again just to know why a wheel exists in the first place.


The Hardest Part: Publishing Workflow

The hardest part wasn't authentication or database design (this 2 were easy due to plugins from established Flask Community, else this should have been the real pain). It was the publishing workflow. I want to do CRUD operations on the fly with as making content upload as easy as possible.

I wanted to write my posts in Markdown and upload them as ZIP files — along with images — and have the blog automatically parse, render, and display them. Out of 6 days that I have given to build this blog, 3 days were spent on this, 1 days on CSS (cause of course I did, and it still looks bland </3 ) Here's what that involved:

  • ZIP extraction: Unzipping the file and finding the .md file inside.
  • Title extraction: Reading the first # line in the Markdown as the post title.
  • Markdown parsing: Rendering the Markdown to HTML with syntax highlighting for code blocks.
  • Image handling: Extracting images, saving them to the server, and rewriting image paths in the Markdown so they point to the correct location.
  • Form validation, authentications, and a CRUD dashboard: To edit some mistakes on the fly.

The image path rewriting was the trickiest part. I had to handle relative paths, absolute paths, and external URLs — all while making sure existing images didn't break.

Here's the core logic I wrote to handle image paths (extracted out):

for img in image_filenames:
    pattern = r"(!\[.*?\]\()(.*?)(" + re.escape(img) + r")(\))"

    def repl(match):
        path = match.group(2)
        if path.startswith(("http://", "https://", "/")):
            return match.group(0)
        new_path = f"/static/post_images/{post_id}/{img}"
        return match.group(1) + new_path + match.group(4)

    content_raw = re.sub(pattern, repl, content_raw)

It's not complete. But it works.


What I Learned

1. Design matters — but you don't need a framework

Or a better way to put it would be, having a rough idea of what the end product should like, why it should look like that, and by when it should be ready to be viewed by other is far more important and hectic than the "how" of the product.

I built the entire design with vanilla CSS. No Bootstrap. No Tailwind. Just CSS variables, flexbox, grid, and a lot of trial and error.

I learned that good design is about consistency, spacing, and typography — not about using a framework. A few well‑chosen fonts, a consistent color palette, and careful attention to padding and margins go a long way.

I learned that Design doesn't have to be UI/UX related but it can be designing the flow, positioning and framing a solution properly, designing the database schema (I am still not sure on this)

2. Deployment is a skill

Deploying the blog to PythonAnywhere taught me about WSGI, environment variables, static file configuration, and setting up a virtual environment. It was frustrating at first — but now I understand how to ship a web application end‑to‑end.

In retrospect however, this was the most easiest part. It was way harder the first time because I was always trapped to the localhost side of things when it comes to websites and smaller components, and I have never implemented anything that interacts with the server anyday.

3. Debugging is part of building

At various points, the blog broke. Images didn't load. Pages returned 500 errors (I did route them nicely later on). The database wouldn't migrate. I learned to interpret error logs of Flask, trace back through the stack, and fix things one step at a time.

It's not glamorous. But it's how you get better.

4. You don't need a complicated stack to ship something real

This was the biggest takeaway. I built a fully functional blog — with admin dashboard, user authentication, tags, and a custom publishing workflow — using just Flask, SQLite, and a bit of JavaScript.

No microservices. No Kubernetes. No complex state management. Just a simple, working application that people can actually use.

5. Analytics taught me how people use what I build

After I deployed the blog, I wanted to know if anyone was actually reading what I wrote. I chose PostHog because it's open-source, developer-friendly, and respects reader privacy — unlike some other analytics tools.

Setting it up was straightforward, and mostly the work is done by an autonomous agent made by them.

Seeing that first real-time event from someone reading my Rust/Zig post was genuinely exciting. It made the blog feel alive — not just a static page, but something people were actually engaging with.

I also learned how to manage environment variables across development and production. That turned out to be a surprisingly useful skill: how to keep API keys and tokens out of your codebase while still making sure they're available when the app runs.

If you're building something — even a small blog — adding analytics can turn a "dumped" project into a "living" one. It's not about tracking people; it's about understanding what works and what doesn't.


Takeaway: Ship It

You don't need to wait until you know everything. You don't need a perfect tech stack. You don't need to understand every concept before you start.

Start with what you know. Build something small. Deploy it. Break it. Fix it. Learn from it.

If you have the entire somewhere in a spec-document or such, try to break it down into weekly or even daily tasks or what I like to call → "Treats" such that you can have a treat daily and be happy that after a few days or weeks you will have something to show for.

That's how you get better. That's how you build something real.

My blog is live. It's not perfect. But it's mine. And I built it from scratch.


Postscript

If you're interested, the full code is open‑source on GitHub

And the live blog is here.

Go ahead, take a look, star the repo, fork it or contribute to it. And if you've been thinking about building something — go build it. You'll learn more than you expect. I think you shouldn't ask for permissions anymore.