ๆƒฏๆ€ง่šๅˆ ้ซ˜ๆ•ˆ่ฟฝ่ธชๅ’Œ้˜…่ฏปไฝ ๆ„Ÿๅ…ด่ถฃ็š„ๅšๅฎขใ€ๆ–ฐ้—ปใ€็ง‘ๆŠ€่ต„่ฎฏ
้˜…่ฏปๅŽŸๆ–‡ ๅœจๆƒฏๆ€ง่šๅˆไธญๆ‰“ๅผ€

ๆŽจ่่ฎข้˜…ๆบ

D
Docker
AI
AI
ๅš
ๅšๅฎขๅ›ญ - ไธ‰็”Ÿ็ŸณไธŠ(FineUIๆŽงไปถ)
่…พ
่…พ่ฎฏCDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Y
Y Combinator Blog
ๅคง็Œซ็š„ๆ— ้™ๆธธๆˆ
ๅคง็Œซ็š„ๆ— ้™ๆธธๆˆ
H
Hackread โ€“ Cybersecurity News, Data Breaches, AI and More
้›ทๅณฐ็ฝ‘
้›ทๅณฐ็ฝ‘
NISL@THU
NISL@THU
S
Schneier on Security
T
Threatpost
T
Tenable Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
ITไน‹ๅฎถ
ITไน‹ๅฎถ
ๅฎ็މ็š„ๅˆ†ไบซ
ๅฎ็މ็š„ๅˆ†ไบซ
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
I
Intezer
Microsoft Azure Blog
Microsoft Azure Blog
ๆœˆๅ…‰ๅšๅฎข
ๆœˆๅ…‰ๅšๅฎข
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
AWS News Blog
AWS News Blog
ๅš
ๅšๅฎขๅ›ญ - Franky
้’›ๅช’ไฝ“๏ผšๅผ•้ข†ๆœชๆฅๅ•†ไธšไธŽ็”Ÿๆดปๆ–ฐ็Ÿฅ
้’›ๅช’ไฝ“๏ผšๅผ•้ข†ๆœชๆฅๅ•†ไธšไธŽ็”Ÿๆดปๆ–ฐ็Ÿฅ
P
Proofpoint News Feed
V
V2EX
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research
Project Zero
Project Zero
PCI Perspectives
PCI Perspectives
G
GRAHAM CLULEY
Help Net Security
Help Net Security
Cloudbric
Cloudbric
Recent Announcements
Recent Announcements
V
Visual Studio Blog
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
C
CERT Recently Published Vulnerability Notes
The Cloudflare Blog
Forbes - Security
Forbes - Security
C
Cisco Blogs
O
OpenAI News
www.infosecurity-magazine.com
www.infosecurity-magazine.com

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Donโ€™t Fail โ€” They Drift Spilling beans for how i learn for exam๐Ÿ˜"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" โ€” What Actually Happened Comfy Cloudโ€™s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions โ€” here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components โ€” Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cรณmo construรญ un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 ๐Ÿš€ I Built an Ethical Hacking Scanner Tool โ€“ Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points โ€” Here's What I Found About How Markets Really Move EcoTrack AI โ€” Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead โ€” I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve โ€” no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like Youโ€™re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace โ€” how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025โ€“62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D โ€” A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent โ€” It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly โ€” 2026/04/10โ€“04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI ้€ฑๅ ฑ โ€” 2026/04/10โ€“2026/04/17 ๆจกๅž‹ๅฐ้Ž–ๆฝฎไพ†ไบ†๏ผŒไฝ†ๅทฅๅ…ท้ˆๆ‰ๆ˜ฏ็œŸๆˆฐๅ ด Maybe this is how Open-Source apps are born... ๐Ÿš€ Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge โ€” $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase โ€” Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train โ€” Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extraรงรฃo de Vรญdeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life โ€” Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 โ€” Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows โ€” Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTrackingๅฎ‰่ฃ…ๅ’ŒiPhone้ขๆ•้…็ฝฎๆ•™็จ‹๏ผŒๆœ‰bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building a Blog Platform with Docker #4: Dynamic Routes and a Real Post List
David Tio ยท 2026-05-10 ยท via DEV Community

David Tio

๐Ÿ—‚๏ธ Building a Blog Platform with Docker #4: Dynamic Routes and a Real Post List

Quick one-liner: Replace the hardcoded route and static post list with a dynamic scanner that finds every .md file in your content folder โ€” drop a file in, it appears on the homepage.


๐Ÿค” Why This Matters

After Episode 3 you can render a Markdown post. One post. At a hardcoded URL.

That's proof the plumbing works โ€” but it's not a blog platform. To have a real blog, two things need to change:

  1. The homepage should list all posts, sorted by date, pulled from actual files โ€” not copied in by hand
  2. The routes should be dynamic โ€” any post at any date and slug should just work, without adding a new @app.route every time

While we're at it, there's a third thing worth doing now: SEO. The moment you have multiple real posts and real URLs, Google can find them. A <meta name="description"> tag is the minimum you need. And since we already have a description field in the post frontmatter (we're adding it today), it costs nothing to wire it up.

By the end of this episode, you'll be able to drop any .md file into content/posts/, refresh the homepage, and see it listed. No code changes required.


๐Ÿ Starting Point

From Episode 3, your structure looks like this:

tiohub-blog/
โ”œโ”€โ”€ app.py
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ static/
โ”‚   โ””โ”€โ”€ js/
โ”‚       โ”œโ”€โ”€ code-blocks.js
โ”‚       โ””โ”€โ”€ tailwind.config.js
โ”œโ”€โ”€ templates/
โ”‚   โ”œโ”€โ”€ index.html
โ”‚   โ””โ”€โ”€ post.html
โ””โ”€โ”€ content/
    โ””โ”€โ”€ posts/
        โ””โ”€โ”€ hey-markdown.md

Enter fullscreen mode Exit fullscreen mode

If you're missing anything, go back to Episode 3 first.


โœ๏ธ Step 1: Update the Frontmatter Schema

We're adding one field to the frontmatter: description. It does two things at once โ€” it becomes the <meta name="description"> tag for SEO, and it becomes the excerpt blurb on the homepage post list.

Update content/posts/hey-markdown.md. Find the existing frontmatter:

---
title: "Hey Markdown"
date: 2026-04-25
tags: [meta, blog]
---

Enter fullscreen mode Exit fullscreen mode

Add the description field between date and tags:

---
title: "Hey Markdown"
date: 2026-04-25
description: "The first post written in Markdown โ€” no more writing HTML by hand."
tags: [meta, blog]
---

Enter fullscreen mode Exit fullscreen mode

This is the full frontmatter schema going forward. Every post should have all four fields:

Field Used for
title <title> tag, <h1> on post page, post list
date URL generation, sorting, display
description <meta name="description">, excerpt on homepage
tags Tag pills on post page, eventually tag pages in Ep12

๐Ÿ” Step 2: Add the Post Scanner

Open app.py. Add a get_all_posts() function below parse_post():

def get_all_posts():
    posts = []
    posts_dir = 'content/posts'
    for filename in os.listdir(posts_dir):
        if not filename.endswith('.md'):
            continue
        filepath = os.path.join(posts_dir, filename)
        post = parse_post(filepath)
        post['slug'] = filename[:-3]
        posts.append(post)
    posts.sort(key=lambda p: p.get('date', ''), reverse=True)
    return posts

Enter fullscreen mode Exit fullscreen mode

What this does:

  • Lists every .md file in content/posts/
  • Parses each one with the existing parse_post() function
  • Adds a slug field (the filename without .md) โ€” this is what goes in the URL
  • Sorts all posts by date, newest first
  • Returns the full list

๐Ÿ”— Step 3: Replace the Hardcoded Route with a Dynamic One

Remove the hardcoded hey_markdown_post() route entirely. Replace it with this:

@app.route('/<int:year>/<int:month>/<slug>')
def post(year, month, slug):
    filepath = f'content/posts/{slug}.md'
    if not os.path.exists(filepath):
        abort(404)
    post = parse_post(filepath)
    return render_template('post.html', post=post)

Enter fullscreen mode Exit fullscreen mode

At the top of app.py, find the existing Flask import:

from flask import Flask, render_template

Enter fullscreen mode Exit fullscreen mode

Add abort to it:

from flask import Flask, render_template, abort

Enter fullscreen mode Exit fullscreen mode

How the URL is constructed: The date in the frontmatter has a year and month attribute once parsed by PyYAML (it parses 2026-04-25 as a Python date object). We'll use those in the next step to build the correct link from the homepage. The route parameters year and month aren't used to look up the file โ€” the slug is enough, since slugs are unique. They're there to keep the URL format consistent with the Blogger pattern we established in Episode 3.


๐Ÿ  Step 4: Update the Homepage

Update the index route in app.py. Find the existing route:

@app.route('/')
def index():
    return render_template('index.html')

Enter fullscreen mode Exit fullscreen mode

Replace it with:

@app.route('/')
def index():
    posts = get_all_posts()
    return render_template('index.html', posts=posts)

Enter fullscreen mode Exit fullscreen mode

Replace the hardcoded post list in templates/index.html:

Find the hardcoded <article> block inside <div class="flex flex-col">:

<article class="border-l-2 border-slate-800 hover:border-brand-500 pl-6 py-5 transition-all duration-300 group cursor-pointer">
    <p class="text-gray-600 text-xs mb-2">29 Mar 2026 &middot; Blog Platform</p>
    <h3 class="text-gray-100 font-semibold text-lg mb-2 group-hover:text-brand-500 transition-colors duration-200">
        <a href="#">Building a Blog Platform #1: Flask Setup</a>
    </h3>
    <p class="text-gray-500 text-sm leading-relaxed">Get a basic Flask app running with separate CSS โ€” no Docker yet, just Python and a stylesheet.</p>
</article>

Enter fullscreen mode Exit fullscreen mode

Replace it with this loop:

{% for post in posts %}
<article class="border-b border-slate-800 pb-10 last:border-0 last:pb-0">
    <div class="flex items-center gap-3 mb-3">
        <time class="text-gray-500 text-sm">{{ post.date }}</time>
        {% for tag in post.tags %}
        <span class="text-xs bg-teal-900/50 text-teal-300 px-2 py-0.5 rounded-full">{{ tag }}</span>
        {% endfor %}
    </div>
    <h2 class="font-serif text-2xl text-white mb-3 leading-snug">
        <a href="/{{ post.date.year }}/{{ '%02d' | format(post.date.month) }}/{{ post.slug }}"
           class="hover:text-teal-400 transition-colors duration-200">
            {{ post.title }}
        </a>
    </h2>
    <p class="text-gray-400 leading-relaxed mb-4">{{ post.description }}</p>
    <a href="/{{ post.date.year }}/{{ '%02d' | format(post.date.month) }}/{{ post.slug }}"
       class="text-teal-400 text-sm hover:text-teal-300 font-medium transition-colors duration-200">
        Read more โ†’
    </a>
</article>
{% endfor %}

Enter fullscreen mode Exit fullscreen mode

Each post in the list now shows:

  • The date and tag pills in a row
  • The post title as a link
  • The description as the excerpt
  • A "Read more โ†’" link

The URL is built from the date and slug: /2026/04/hey-markdown. No hardcoding anywhere.


๐Ÿ”Ž Step 5: Add Meta Description to the Post Page

Open templates/post.html. In the <head> section, add the meta description tag after <title>:

<title>{{ post.title }}</title>
<meta name="description" content="{{ post.description }}">

Enter fullscreen mode Exit fullscreen mode

That's it. When Google indexes the page, it reads this tag and uses it as the snippet under the link in search results. The content comes directly from the description field in your frontmatter โ€” the same text you already wrote for the homepage excerpt.


๐Ÿท๏ธ Step 6: Add Tag Pills to the Post Page

Open templates/post.html. Inside <main>, find the date and title block:

<p class="text-gray-500 text-sm mb-4">{{ post.date }}</p>
<h1 class="font-serif text-4xl text-white leading-tight mb-8">{{ post.title }}</h1>

Enter fullscreen mode Exit fullscreen mode

Replace it with this โ€” same date and title, with the tag pills inserted between them:

<p class="text-gray-500 text-sm mb-3">{{ post.date }}</p>
<div class="flex flex-wrap gap-2 mb-6">
    {% for tag in post.tags %}
    <span class="text-xs bg-teal-900/50 text-teal-300 px-2.5 py-1 rounded-full">{{ tag }}</span>
    {% endfor %}
</div>
<h1 class="font-serif text-4xl text-white leading-tight mb-8">{{ post.title }}</h1>

Enter fullscreen mode Exit fullscreen mode

Tags are non-clickable for now โ€” they're labels, not links. They become proper links with their own pages in Episode 12, once the platform has enough posts and structure to make tag filtering useful.


๐Ÿงช Step 7: Test It

Add a second post so you can verify the list actually works. Create content/posts/second-post.md:

---
title: "Second Post"
date: 2026-04-26
description: "Proving the scanner works โ€” this post appeared without touching app.py."
tags: [test]
---

Dropped this file into `content/posts/`. Restarted Flask. It appeared.

No new routes. No hardcoded HTML. Just a file.

Enter fullscreen mode Exit fullscreen mode

Run the app:

$ python app.py

Enter fullscreen mode Exit fullscreen mode

Check:

  • Homepage โ€” both posts listed, newest first, with excerpts and tags
  • /2026/04/hey-markdown โ€” first post renders correctly
  • /2026/04/second-post โ€” second post renders correctly
  • View source on either post โ€” confirm <meta name="description"> is present in <head>
  • Drop another .md file into content/posts/ and refresh โ€” it should appear immediately

โœ… What You've Built

Your file structure now:

tiohub-blog/
โ”œโ”€โ”€ app.py
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ static/
โ”‚   โ””โ”€โ”€ js/
โ”‚       โ”œโ”€โ”€ tailwind.config.js
โ”‚       โ””โ”€โ”€ code-blocks.js
โ”œโ”€โ”€ templates/
โ”‚   โ”œโ”€โ”€ index.html
โ”‚   โ””โ”€โ”€ post.html
โ””โ”€โ”€ content/
    โ””โ”€โ”€ posts/
        โ”œโ”€โ”€ hey-markdown.md
        โ””โ”€โ”€ second-post.md

Enter fullscreen mode Exit fullscreen mode

What changed:

โœ… get_all_posts() โ€” scans the posts folder and returns all posts sorted by date
โœ… Dynamic /<int:year>/<int:month>/<slug> route โ€” works for any post, no code changes needed
โœ… Homepage driven by real files โ€” no more hardcoded HTML
โœ… description field wired up as SEO meta tag and homepage excerpt
โœ… Tag pills on post pages


๐Ÿš€ Coming Up

Next episode: Docker. The app runs fine locally, but "works on my machine" isn't a deployment strategy. We'll write a Dockerfile, build an image, and run the blog in a container โ€” the same way it'll run in production.


Found this helpful? Share it with your network or drop a comment below.


SEO Metadata

  • Title: Building a Blog Platform with Docker #4: Dynamic Routes and a Real Post List
  • Meta Description: Replace hardcoded Flask routes with a dynamic post scanner. Every .md file in your content folder becomes a post automatically โ€” plus SEO meta description and tag pills.
  • Word Count: ~1,100