JUL 09, 20265 MIN READ Markdown

A fast blogging platform with Go, Nuxt, Caddy and Docker.

I built Typen, a clean blogging platform with Go, Nuxt, Caddy, Docker, Redis, MySQL, and Meilisearch. It focuses on simple publishing, SEO-ready blogs, and Cleo, an AI reading assistant without AI bloat.

Martin Binder

Martin Binder

ID @martin

A fast blogging platform with Go, Nuxt, Caddy and Docker.

Typen: AI Without the Bloat

Typen also includes Cleo, an AI reading assistant built directly into blog posts.

But I do not want Typen to become another AI content generator.

The goal is not to generate endless content. AI should only help where it is genuinely useful:

  • Helping readers understand posts

  • Answering questions about an article

  • Helping readers get more value from what they are already reading

Typen is available at typen.blog.

Tech Stack

The current stack includes:

  • Go backend

  • Nuxt dashboard

  • Nuxt blog frontend

  • Caddy reverse proxy

  • Docker

  • Docker Compose

  • MySQL

  • Redis

  • Meilisearch

  • GitHub Actions CI/CD

  • VPS deployment

The application is split into multiple services:

app/
├── server/        # Go API
├── client/        # Nuxt dashboard
├── blogclient/    # Nuxt public blog frontend
├── sitemap/       # XML sitemap generator
├── dbadmin/       # Database administration utility
└── docker/
    └── caddy/     # Reverse proxy and routing

Architecture

Typen has one main Go API and two Nuxt frontends.

The client application is the dashboard where users manage their blogs.

The blogclient application renders public blogs.

Both frontends communicate with the same Go backend.

Caddy sits in front of the services and handles routing, custom domains, subdomains, and HTTPS.

User
  ↓
Caddy
  ↓
 ┌──────────────┬─────────────────┐
 │ Dashboard    │ Public Blogs    │
 │ Nuxt client  │ Nuxt blogclient │
 └──────┬───────┴────────┬────────┘
        ↓                ↓
              Go API
         ↓       ↓       ↓
       MySQL   Redis   Meilisearch

Docker-Native Deployment

Everything runs using Docker and Docker Compose.

For production, the application can be started with:

docker compose up -d

For development, I use a separate Compose file:

make dev

The Makefile also includes commands for common tasks:

env:
	@cp -n .env.example .env || true

dev:
	@docker compose -f compose-dev.yaml up --build

prod:
	@docker compose up -d

deploy:
	@git pull origin main
	@APP_ENV=prod docker compose up -d --build
	@docker compose exec sitemap ./sitemap-worker generate
	@docker image prune -f

logs:
	@docker compose logs -f $(SERVICE)

ps:
	@docker compose ps

down:
	@docker compose down

test:
	@go test -C ./server ./...

lint:
	@cd ./server && golangci-lint run

fmt:
	@cd ./server && go fmt
	@cd ./client && pnpm run format
	@cd ./blogclient && pnpm run format

CI/CD With GitHub Actions

I use GitHub Actions to build Docker images and push them to GitHub Container Registry.

The workflow checks which directories changed and rebuilds only the affected services:

  • Changes in server/** rebuild the Go API

  • Changes in client/** rebuild the dashboard

  • Changes in blogclient/** rebuild the public blog frontend

  • Changes in sitemap/** rebuild the sitemap service

There is also a manual force_rebuild option for rebuilding every service from scratch.

After the images are built, the workflow connects to the VPS over SSH and runs:

cd /srv/app
git pull
docker compose pull
APP_ENV=prod APP_VERSION=<short-sha> docker compose up -d
docker image prune -a -f

It is a simple setup, but it works well for a bootstrapped project.

SEO and Sitemaps

SEO is an important part of Typen.

Each blog can generate its own sitemap. The sitemap service is written in Go and creates XML sitemap files for published posts and tags.

It can split large sitemaps into multiple files using the standard limit of 50,000 URLs per file.

A simplified version looks like this:

const maxURLsPerFile = 50000

For each blog with sitemaps enabled, the service:

  • Loads the blog’s custom domain or subdomain

  • Fetches published posts

  • Fetches tags

  • Writes the sitemap XML files

  • Writes a sitemap index

Generated URLs include posts such as:

https://example.com/posts/my-post

And topics such as:

https://example.com/topic/go

Themes With CSS Variables

Typen uses CSS variables for blog customization.

This keeps themes simple and flexible without requiring a complex theme engine.

Each blog can have its own colors, styles, and visual settings while keeping the frontend fast and maintainable.

The goal is to let users customize their blogs without turning Typen into a large website builder.

Local Development

For local development, Typen uses subdomain-based routing.

I add local domains to /etc/hosts:

127.0.0.1   typen.local
127.0.0.1   watches.typen.local blog.typen.local

Then I can open:

http://typen.local

for the dashboard, and:

http://watches.typen.local

for a local test blog.

Current Stage

Typen is still early, but the main structure is already working:

  • Dashboard

  • Public blog frontend

  • Go backend

  • Docker deployment

  • CI/CD

  • Sitemap generation

  • Caching

  • Themes

  • Markdown posts

  • Cleo AI reading assistant

I am preparing for a bigger launch, but first I want to collect feedback from developers, creators, bloggers, and indie makers.

What I Would Love Feedback On

I would appreciate feedback on:

  • The idea

  • The technical architecture

  • The UX

  • The positioning

  • The SEO features

  • The AI reading assistant

  • What would make you use a blogging platform today

Final Thoughts

I do not want Typen to become a bloated website builder or another AI content machine.

I want it to be a clean, fast place where people can write, publish, and share ideas, with AI used only where it genuinely helps the reader.

Thanks for reading.


Comments

Sign in to comment. Sign in

No comments yet.