A hybrid headless setup that allows you to design in Webflow, write in Notion, and serve everything via Cloudflare Workers using server-side rendering (HTMLRewriter).
This is the source code behind sil.so. For the full backstory and architecture breakdown, read the blog post here.
- 🎨 Webflow for Design: Complete visual control without coding the UI.
- 📝 Notion as CMS: A seamless writing experience.
- ⚡ Cloudflare Workers: Server-side rendering (no client-side loading spinners).
- 🚀 KV Caching: Sub-50ms load times after the first hit.
- 🔍 Automatic SEO: Auto-generates JSON-LD Schema, Open Graph tags, and Twitter cards.
The worker automatically parses and renders these Notion blocks into semantic HTML:
| Notion Block | Rendered HTML | Notes |
|---|---|---|
| Paragraph | <p> |
Supports bold, italic, links, etc. |
| Headings | H2, H3, H4 |
Maps H1 -> H2 for better SEO structure. |
| Lists | <ul>, <ol> |
Supports nested lists. |
| Toggle | <details> |
Converts to an interactive Accordion. |
| Quote | <blockquote> |
|
| Image | <figure> |
Includes captions and lazy loading. |
| Video | <video> |
Native HTML5 video player. |
| Code | <pre><code> |
Prism.js syntax highlighting + Copy button. |
| Code (HTML Embed) | Raw HTML | Set caption to "Embed" to render raw HTML/Scripts. |
| Divider | <hr> |
Uses Webflow's separator class. |
- Cloudflare Account (Free tier works).
- Notion Account with a created Integration.
- FlowTube Extension (for syncing Webflow code to GitHub).
- Duplicate this Notion Template.
- Create an Internal Integration at Notion My Integrations.
- Give the integration access to the specific database you just duplicated.
- Copy your Internal Integration Secret (API Key).
You need to add specific attributes to your Webflow elements so the Worker knows where to inject content.
Design a static list with one item (an article card). Add these attributes:
| Element | Attribute |
|---|---|
| Article Wrapper | data-template="item" |
| Title Text | data-bind="title" |
| Date Text | data-bind="date" |
| Link Block | data-bind="link" |
| Image | data-bind="image" |
Design the skeleton of your post. Add these IDs:
| Element | ID |
|---|---|
| H1 Heading | post-title |
| Date Text | post-published |
| Rich Text | post-content |
| Cover Image | post-banner |
1. Schema Tag:
Add this to the <head> of both pages (Custom Code section) for automatic SEO injection:
<script type="application/ld+json"></script>2. Active State Script:
Add this to the <body> footer of the Blog Post page to highlight the "Blog" nav link when reading a post:
<script>
(function () {
function fixBlogNav() {
document.querySelectorAll("a.nav-link").forEach(function (link) {
var href = link.getAttribute("href") || "";
if (
href === "/blog" ||
href === "/blog/" ||
(href.indexOf("blog") > -1 && href.indexOf("blog/") === -1)
) {
link.classList.add("w--current");
link.setAttribute("aria-current", "page");
}
});
}
if (document.readyState === "complete") {
setTimeout(fixBlogNav, 0);
} else {
window.addEventListener("load", function () {
setTimeout(fixBlogNav, 0);
});
}
})();
</script>- Clone this repo.
- Edit
worker.js: Update theSITE_CONFIGobject at the top with your details (Domain, Name, Socials). - Setup KV Storage:
- In Cloudflare Dashboard: Workers & Pages -> KV.
- Create namespace
BLOG_CACHE. - Copy the ID and paste it into
wrangler.jsonc:
"kv_namespaces": [
{
"binding": "BLOG_CACHE",
"id": "PASTE_YOUR_ID_HERE"
}
]- Go to Cloudflare Dashboard -> Workers & Pages.
- Create Application -> Connect to GitHub -> Select this repo.
- Set the Deploy command:
npx wrangler deploy --env production - Add Secrets:
Once the project is created, go to Settings -> Variables and Secrets and add:
NOTION_API_KEY: Your Notion Integration Secret.NOTION_DB_BLOG_ID: The ID from your Notion Database URL (the 32-character string before the?).
The site uses KV caching to stay fast. Changes in Notion won't appear immediately. To force an update, append ?refresh=true to any URL.
Example: https://yourdomain.com/blog/my-post?refresh=true