You Don't Need A Framework - Build A Dynamic Blog with HTML & Vanilla JavaScript

Months ago, when I was reworking parts of my website, I began questioning every part of the tech stack. During that process, I thought about how I would build a dynamic blog with vanilla JavaScript. Eventually, I realized I was still going to end up using a framework. So, I worked through the main concepts in my head to scratch the itch, then shelved the idea...until, I couldn't ignore the fact that the itch was still there.
This article is simply about the JavaScript side of building a dynamic blog. It is not a claim on the best approach for such a system, nor an argument for or against using vanilla JS or a framework. We're just looking at how this could get done.
Let's get into it
First things first, we need a few HTML and JS files, so let's get a new project going in your favorite editor (feel free to code along).
Create a new folder, name it whatever you want, and open it in your editor. Create the following files and folders:
index.html
blog.html
blog/post.html
js/blogCards.js
js/blogPost.js
js/blogPostsData.js
No Webpack, no Vite, no package.json... and no Tailwind ๐คฏ. Ahhh, how refreshing.
Boilerplate code
Here is the relevant HTML
blog.html:
Include the script tag for
blogCards.js
in the head
<script type="module" src="/js/blogCards.js" async></script>
blog-post.html:
Include the script tag for
blogPost.js
in the head
<script type="module" src="/js/blogPost.js" async></script>
Also, the blog post data
blogPosts.js:
Add as many blog post objects as you want. I had ChatGPT generate some dummy data here.
Start and preview
This is a good time to start the project to see what we have so far. If you're using something like VS Code, view the project using Live Server.
Dynamically populate the blog listing page with blog-post cards
Let's start by importing the data at the top of the blogCards.js
file:
import { blogPosts } from "./blogsPosts.js"
We're going to have to place each blog card inside of a the container element we created in the blog listing page, so let's declare a blogCardsContainer
variable and store a reference to the container in it:
const blogCardsContainer = document.querySelector("#blog-cards-container");
Now, we can loop over each blog post in our data, create the cards, and then finally append them to blogCardsContainer
:
The first three lines inside of the forEach loop:
Creates a div element to be styled as the card
Adds a "blog-card" class to the div
Sets a "data-id" attribute on the div with the value of current post id to uniquely identify each blog post card (in case you want control for individual cards).
Here's one of the most important parts of the blogCardHTML
content we created above: We set the href attribute to the individual post.html page, but, and here it is, we add in a slug
parameter which is the value of the blog post slug. This is how the individual post.html page knows what blog post it's supposed to be loading when navigated to.
Now that we know which post is supposed to be showing, all we have to do is create the HTML structure for our blog post and load in the post data, similar to how we did for the blogCards - except we're not doing a loop this time. Let's apply the logic...
Dynamically building the individual blog post page
When our post.html file loads, we already know that we're going to have a post slug as a parameter. This is how we know which post to work with, so let's set it up.
- At the top of blogPost.js, import
blogPosts
You may know about the window.location
object. But, to isolate the query parameters from the URL, we can access the search
property on window.location, and we'll pass that into the URLSearchParams
interface:
const params = new URLSearchParams(window.location.search);
The URLSearchParams interface exposes a 'get' utility method allowing us to retrieve the exact parameter we're looking for:
const slug = params.get("slug");
Perfect. Now, let's find the exact post:
const blogPost = blogPosts.find((post) => post.slug === slug);
Like with the blogCards, let's grab the blog post container on our post.html file so that we know where to inject the post after we build it:
const blogPostContainer = document.querySelector("#blogPost");
Okay, now let's build the blog post HTML structure, and inject into the blog post container, but only on the condition that the post was actually found. For now, we handle the situation where the post isn't found by displaying some simple text.
Try it out
Go back to the blog listing page. Navigate to different posts, and watch them be displayed onto the page.
๐ Congrats! You now know some essential concepts needed to build a dynamic blog system in vanilla JavaScript ๐
If all this has made you curious to find out how much more it would take to get this blog to a complete, production-ready level, I encourage you to scratch that itch - It'll be worth it.
Here's a hint to some modifications and optimizations that would make this project better:
SPA experience
load all content and pages using a single div in the index.html file, just like frameworks do
this will offer better URLs and faster load times since you won't be loading full pages
Store blog posts in an external db
Use a mouseover event listener on blog post cards to prefetch html content
Conclusion
Although there are many changes you could make to this project, the point is you don't always need every framework feature. Often, all you need is simplicity.
If this article helped you learn something, I'm so glad. But there are three important takeaways I hope youโll leave with:
Remember to question things from time to time.
Go beyond the question, and let your curiosity scratch the itch (think about the solution)
You don't always need a framework ๐๏ธ
Subscribe to my newsletter
Read articles from Patricio Salazar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Patricio Salazar
Patricio Salazar
I'm a Software Developer with a full-time "9-5" working on Side Projects and sharing about it.