How to Create a Dynamic Blog on Neocities
Dynamic blog on a static site? It's easier than you think.
One of the biggest limitations of static hosts like Neocities is that there's no database and no server-side code.
At first glance, that seems like it rules out things like:
blogs
linklogs
status updates
changelogs
bookmarks
But here's the trick: Let GitHub host your data and let JavaScript load it. Using a GitHub Gist as a tiny database, you can update your content from anywhere and have your Neocities site automatically display the latest version. No PHP. No Node. No backend. Just HTML, JavaScript, JSON, and a little ingenuity. What we're building
We'll create:
/links/ |--- index.html |--- add.html
And we'll store our post data in a GitHub Gist:
JSON
[
{
"title": "Neocities",
"url": "https://neocities.org",
"description": "Free static site web hosting with no ads.",
"date": "2026-08-27"
}
]
Whenever the Gist is updated, the site updates automatically.
Step 1: Create a GitHub Gist
Go to:
https://gist.github.com
.
Create a new public Gist.
Name the file: links.json.
Add some sample data:
JSON
[
{
"title": "Neocities",
"url": "https://neocities.org",
"description": "Free static site web hosting with no ads.",
"date": "2026-08-27"
}
]
Save it.
Step 2: Get Your Gist ID
Your Gist URL will look something like:
https://gist.github.com/username/d8cd18b09cfaf82f61e4e647798156c1
Copy the long ID:
d8cd18b09cfaf82f61e4e647798156c1.
Step 3: Create index.html
Create a place for the links to appear:
Then add JavaScript:
How to Create a Dynamic Blog on Neocities
Dynamic blog on a static site? It's easier than you think.
One of the biggest limitations of static hosts like Neocities is that there's no database and no server-side code.
At first glance, that seems like it rules out things like blogs, linklogs, status updates, changelogs, and bookmarks.
But here's the trick:
Let GitHub host your data and let JavaScript load it.
Using a GitHub Gist as a tiny database, you can update your content from anywhere and have your Neocities site automatically display the latest version.
No PHP. No Node. No backend.
Just HTML, JavaScript, JSON, and a little ingenuity.
What We're Building
/links/ ├── index.html └── add.html
We'll store our content in a GitHub Gist:
[
{
"title": "Neocities",
"url": "https://neocities.org",
"description": "Free static site web hosting with no ads.",
"date": "2026-08-27"
}
]
Whenever the Gist is updated, the site updates automatically.
Step 1: Create a GitHub Gist
Visit:
https://gist.github.com
Create a new public Gist and name the file:
links.json
Add some sample data:
[
{
"title": "Neocities",
"url": "https://neocities.org",
"description": "Free static site web hosting with no ads.",
"date": "2026-08-27"
}
]
Step 2: Get Your Gist ID
Your Gist URL will look something like:
https://gist.github.com/username/d8cd18b09cfaf82f61e4e647798156c1
Copy the long ID:
d8cd18b09cfaf82f61e4e647798156c1
Step 3: Create index.html
Create a container for your links:
<div id="links"></div>
Then add JavaScript to pull the latest version of your Gist through GitHub's API:
<script>
const GIST_ID =
"d8cd18b09cfaf82f61e4e647798156c1";
async function loadLinks() {
const gist = await fetch(
`https://api.github.com/gists/${GIST_ID}`
).then(r => r.json());
const rawUrl =
gist.files["links.json"].raw_url;
const links =
await fetch(rawUrl).then(r => r.json());
const container =
document.getElementById("links");
container.innerHTML = "";
links.slice().reverse().forEach(link => {
const el = document.createElement("article");
el.innerHTML = `
<h2>
<a href="${link.url}">
${link.title}
</a>
</h2>
<small>${link.date}</small>
<p>${link.description}</p>
`;
container.appendChild(el);
});
}
loadLinks();
</script>
The cool part: GitHub automatically gives us the newest raw URL every time we load the page, so we never have to update links manually.
Step 4: Create add.html
Let's build a helper page to generate new entries.
<input id="title">
<input id="url">
<textarea id="description"></textarea>
<button onclick="generate()">
Generate JSON
</button>
<pre id="output"></pre>
Now add:
<script>
function generate() {
const title =
document.getElementById("title").value;
const url =
document.getElementById("url").value;
const description =
document.getElementById("description").value;
const date =
new Date().toISOString().slice(0,10);
const json =
JSON.stringify({
title,
url,
description,
date
}, null, 2) + ",";
document
.getElementById("output")
.textContent = json;
navigator.clipboard.writeText(json);
}
</script>
Now you can:
- Enter a link.
- Click Generate.
- The JSON is copied to your clipboard.
- Paste it into your Gist.
- Save.
Step 5: Add a Shortcut to Your Gist
Create a big obvious button:
<a href="https://gist.github.com/username/YOUR_GIST_ID/edit"
target="_blank">
Open Gist Editor
</a>
Your publishing workflow now looks like:
Visit add.html
↓
Generate JSON
↓
Copy
↓
Open Gist Editor
↓
Paste
↓
Save
Why This Works
Neocities remains completely static.
GitHub stores your content.
JavaScript stitches them together in the browser.
The result feels like a dynamic blog even though there is no database and no server-side code anywhere.
This same pattern works for:
- Blogs
- Linklogs
- Bookmarks
- Status updates
- Reading journals
- Project logs
- Changelogs
Bonus: Embed It Anywhere
You don't even have to make it a standalone page.
If your linklog lives at:
/links/index.html
You can embed it elsewhere with an iframe:
<iframe
src="/links/index.html"
width="100%"
height="800">
</iframe>
This lets you maintain a single source of content while displaying it across multiple pages on your site.
Conclusion
Congratulations. You've just built a lightweight CMS using nothing but Neocities, GitHub Gist, JSON, and a little client-side JavaScript.
No backend. No database. No framework.
Just static files, clever hacks, and the open web.
Pretty neato burrito. 🌯
Making It Look Like a Real Blog
At this point you have a working dynamic blog, but it probably looks like a plain list of links. Let's fix that.
One of the nice things about this setup is that your content and presentation are completely separate. You can redesign the site whenever you want without touching your JSON data.
Here's a simple blog-style layout:
body {
margin: 0;
padding: 30px;
background:
linear-gradient(
to bottom,
#f0f0f0,
#d8d8d8
);
color: #222;
font-family:
Verdana,
Arial,
sans-serif;
line-height: 1.5;
}
#page {
max-width: 860px;
margin: 0 auto;
}
article {
background: white;
border: 1px solid #bbb;
border-top: 4px solid #3388ff;
padding: 18px;
margin-bottom: 20px;
box-shadow:
0 2px 8px rgba(0,0,0,.15);
}
article h2 {
margin: 0 0 6px 0;
}
article h2 a {
color: #3388ff;
text-decoration: none;
}
article h2 a:hover {
color: #ff0066;
text-decoration: underline;
}
small {
color: #8833ee;
font-weight: bold;
}
.url {
font-size: 11px;
color: #666;
}
This creates the classic early-2000s web magazine look:
- Boxed articles
- Colored borders
- Subtle drop shadows
- Blue headlines
- Strong datelines
Building a Masthead
A good blog deserves a proper logo.
Instead of a plain heading, try something like this:
<h1>
Links
<span class="tagline">
from the underground
</span>
</h1>
Then style the tagline to look like a piece of fluorescent tape stuck onto your logo:
.tagline {
display: inline-block;
margin-left: 16px;
padding: 6px 16px;
background:
linear-gradient(
to bottom,
#fffd77,
#fff040
);
color: #111;
font-family:
"Trebuchet MS",
sans-serif;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
transform: rotate(-4deg);
box-shadow:
0 2px 0 rgba(0,0,0,.3),
3px 4px 8px rgba(0,0,0,.25);
}
Now it looks less like a webpage and more like somebody slapped a piece of painter's tape onto a glossy magazine logo with a Sharpie.
Add a Dateline
You can automatically display today's date:
<div class="edition">
<span class="edition-label">
EDITION
</span>
<span id="dateline"></span>
<span class="edition-sep">
•
</span>
<span class="edition-note">
crafted by human hands • updated frequently
</span>
</div>
And the JavaScript:
function updateDateline() {
const now = new Date();
document.getElementById("dateline")
.textContent =
now.toLocaleDateString(
"en-US",
{
month: "long",
day: "numeric",
year: "numeric"
}
);
}
updateDateline();
The result looks something like:
EDITION August 29, 2026 • crafted by human hands • updated frequently
Little touches like this help transform a simple JSON feed into something that feels like a real publication.