Blogo is a production-ready blog that demonstrates what you can build with hsx, a server-side JSX runtime for Deno. It proves you don't need React, Next.js, or any client-side framework to build fast, modern web applications.
v2 adds static site generation and AT Protocol integration, giving you decentralized content ownership through the Standard.site lexicon.
See it live: blogo.timok.com
hsx is a server-side JSX runtime that gives you React's developer experience without shipping JavaScript to the browser. Write JSX components, get HTML strings. Zero client-side rendering. Zero hydration. Zero bundle.
// This is hsx, not React
const PostCard = ({ title, excerpt }: Props) => (
<article class="post-card">
<h2>{title}</h2>
<p>{excerpt}</p>
</article>
);
// Renders to pure HTML string on the server
const html = renderVNode(<PostCard title="Hello" excerpt="World" />);
// Output: <article class="post-card"><h2>Hello</h2><p>World</p></article>The browser receives HTML. Not JavaScript instructions to build HTML. Just HTML.
Modern web development has become absurdly complex. React alone ships ~40KB minified, and that's before your app code. Then you need a meta-framework for SSR, a bundler, a build step, hydration logic, and state management. For what? To render HTML.
hsx takes a different path:
- Zero client-side JavaScript for rendering - The browser receives HTML, ready to display
- Familiar JSX syntax - Write components exactly like React, minus useState/useEffect complexity
- Server-side composition - Components compose where your data lives
- Type safety - Full TypeScript support with proper JSX types
- HTMX synergy - Semantic aliases make HTMX feel native to JSX
Blogo demonstrates how hsx and HTMX work together. hsx renders the initial HTML, HTMX handles dynamic updates without page reloads:
// hsx provides semantic aliases for HTMX attributes
<a
href="/posts"
get="/posts" // Maps to hx-get
target="#content" // Maps to hx-target
swap="innerHTML" // Maps to hx-swap
pushUrl="true" // Maps to hx-push-url
>
Load Posts
</a>;The result feels like a SPA but works without JavaScript. Progressive enhancement, not progressive complexity.
Components return VNodes (virtual DOM nodes). The renderVNode() function
recursively converts them to HTML strings:
// Component tree
const Page = () => (
<Layout title="Home">
<PostList posts={posts} />
</Layout>
);
// Convert to HTML
const htmlString = renderVNode(<Page />);
// For pre-rendered HTML (like markdown), bypass escaping
import { html } from "./render-vnode.ts";
{
html(post.content);
} // Renders as-isThis blog showcases hsx capabilities in a real application:
- Server-side JSX components - Layout, PostView, PostList, TagCloud, SearchResults
- HTMX integration - SPA-like navigation without client-side routing
- Markdown rendering - Using
html()for safe raw HTML injection - Dynamic content - Search, filtering, pagination via HTMX partial updates
- Progressive enhancement - Works fully without JavaScript, enhanced with HTMX
- Markdown posts with YAML frontmatter
- Tag-based categorization and filtering
- Full-text search with modal and results page
- Light/dark theme toggle
- Syntax highlighting with Highlight.js
- Mermaid diagram support
- RSS feeds
- SEO optimization with structured data
- Static site generation (
deno task build) - AT Protocol publishing via Standard.site lexicon
- Deno v2.x or higher
# Clone and setup
git clone https://github.com/srdjan/blogo.git
cd blogo
deno task setup
# Start development server
deno task devOpen http://localhost:8000
deno task dev # Development server with hot reload
deno task start # Production server
deno task build # Generate static site to _site/
deno task test # Run tests
deno task check # Type check
deno task fmt # Format code
deno task lint # Lint codeThese require AT Protocol credentials (see docs/pds-guide.md):
deno task at:publish # Publish all posts to your PDS
deno task at:pull # Pull documents from PDS to local markdown
deno task at:verify # Verify well-known endpoint and link tagsdeno task build renders every route (pages, fragments, feeds, sitemaps) into
a _site/ directory that can be deployed to any static host. HTMX navigation
continues to work via pre-rendered fragment files.
deno task build
# Build complete: 98 pages, 42 fragmentsWhen AT Protocol environment variables are set at build time, the static output
includes the .well-known/site.standard.publication endpoint and per-post
<link rel="site.standard.document"> tags.
Blogo can publish your posts as site.standard.document records on any AT
Protocol Personal Data Server. This gives you decentralized ownership of your
content - your posts live on the AT Protocol network alongside the rendered
blog.
The integration is opt-in. Set three environment variables to enable it:
ATPROTO_DID=did:plc:your-did
ATPROTO_HANDLE=yourhandle.bsky.social
ATPROTO_APP_PASSWORD=xxxx-xxxx-xxxx-xxxxWith credentials configured, the server exposes a verification endpoint at
/.well-known/site.standard.publication and each post page includes a
<link rel="site.standard.document"> tag linking to the corresponding PDS
record.
See docs/pds-guide.md for the full setup guide, including credential setup, publishing, pulling, and verification.
src/
app/
main.ts # Server entry point
build.ts # Static site generator
at-publish.ts # CLI: publish posts to PDS
at-pull.ts # CLI: pull documents from PDS
at-verify.ts # CLI: verify AT Protocol setup
http/ # Routes, server, middleware
render-vnode.ts # hsx VNode to HTML renderer
components/ # JSX components (Layout, PostView, etc.)
domain/ # Business logic (content, atproto, static-builder)
atproto/ # Standard.site lexicon types and mapping
config/ # Configuration (atproto env vars)
ports/ # Dependency interfaces (filesystem, cache, atproto, writer)
lib/ # Core utilities (Result types, etc.)
content/posts/ # Markdown blog posts
public/ # Static assets
docs/ # Guides (PDS setup)
Add markdown files to content/posts/:
---
title: Your Post Title
date: 2025-01-15
tags: [Technology, Tutorial]
excerpt: Brief description
---
# Your Content
Write markdown here. Supports code blocks, Mermaid diagrams, images.Blogo deploys to Deno Deploy with zero configuration:
- Push to GitHub
- Connect repository at dash.deno.com
- Set entry point:
src/app/main.ts - Deploy
Every push to main triggers automatic deployment.
Alternatively, generate a static site with deno task build and deploy the
_site/ directory to any static host (Netlify, Cloudflare Pages, S3, etc.).
| Layer | Technology |
|---|---|
| Runtime | Deno v2.x |
| Rendering | hsx (server-side JSX) |
| Interactivity | HTMX v2.x |
| Styling | Modern CSS (nesting, container queries, logical properties) |
| Content | Markdown with marked |
| Syntax | Highlight.js |
| Diagrams | @rendermaid/core |
| Protocol | AT Protocol (Standard.site lexicon) |
| Hosting | Deno Deploy / any static host |
Built with hsx by Claude, GPT, and Srdjan.
Fork it, deploy it, make it yours.
MIT
