A content-first personal site built with Astro, Hono, and Cloudflare D1, rendered entirely at the edge (SSR) on Cloudflare Workers. It ships zero client-side JavaScript by default, is themed with the Catppuccin palette, and has full Turkish character support.
DESIGN.md is the source of truth for the database schema, color tokens, typography, and identity system. AGENTS.md holds the hard architectural constraints. This README is the onboarding guide.
| Concern | Choice |
|---|---|
| Framework | Astro 7 (SSR, output: 'server') |
| Edge runtime / deploy | Cloudflare Workers via @astrojs/cloudflare |
| API | Hono, embedded in an Astro API route |
| Database | Cloudflare D1 (SQLite) |
| Media | Cloudflare R2 |
| Interactive UI | Preact islands (only when genuinely needed) |
| Styling | Tailwind CSS v4 + Catppuccin Macchiato/Latte tokens |
| Fonts | Self-hosted Google Fonts (@fontsource), latin-ext subset |
src/
components/ui/ pure .astro (Header, Footer, PostCard)
components/islands/ Preact .tsx (interactive only)
layouts/ BaseLayout.astro, PostLayout.astro
pages/api/ embedded Hono router ([...path].ts)
pages/blog/ [slug].astro (Edge SSR post)
pages/index.astro post listing
styles/ Tailwind + global CSS + Catppuccin tokens
lib/ types, D1 query helpers, markdown renderer
schema.sql D1 schema (mirrors DESIGN.md §2)
seed.sql seed posts (generated from _raw_posts/*.md)
_raw_posts/ Turkish source markdown (seed reference; do not edit casually)
wrangler.toml Worker config + D1/R2 bindings
astro.config.mjs adapter, integrations, platformProxy
- Node.js 22+
- pnpm 10+
- A Cloudflare account, authenticated locally via
wrangler login - A D1 database and an R2 bucket (see first-time setup)
pnpm install
# Create the remote resources once:
wrangler d1 create hex # -> copy the printed database_id into wrangler.toml
wrangler r2 bucket create hex-assets
# Apply schema + seed posts to the PRODUCTION database:
pnpm db:push # wrangler d1 execute hex --remote --file=./schema.sql
pnpm db:seed # wrangler d1 execute hex --remote --file=./seed.sql
pnpm devPut the
database_idreturned bywrangler d1 create hexintowrangler.tomlunder[[d1_databases]].
| Command | What it does |
|---|---|
pnpm dev |
Astro dev server. With remoteBindings: true, it reads the production D1/R2 bindings (no local stub data to maintain). |
pnpm build |
Production build → dist/ (dist/client assets + dist/server Worker). |
pnpm check |
Typecheck (astro check, not tsc). |
pnpm preview |
Run the built Worker locally (wrangler dev --config dist/server/wrangler.json). |
pnpm deploy |
Deploy the Worker (wrangler deploy --config dist/server/wrangler.json). |
pnpm db:push |
Apply schema.sql to the remote D1. |
pnpm db:seed |
Apply seed.sql to the remote D1 (idempotent, slug-scoped). |
pnpm cf-typegen |
Regenerate worker-configuration.d.ts after editing wrangler.toml. |
- Schema:
schema.sqlmirrorsDESIGN.md§2 (posts,tags,post_tags,guestbook,kv_store). Apply withpnpm db:push. - Seed:
seed.sqlis generated from_raw_posts/*.md(title →title, body →content_markdown,description→excerpt,publishDate→published_at, filename →slug). It clears its known slugs before inserting, so it is safe to re-run. To regenerate after editing source posts, re-run the frontmatter extractor that produced it. - Accessing bindings: always via
import { env } from 'cloudflare:workers'→env.DB/env.BUCKET. D1 helpers insrc/lib/db.tstake aD1Databaseso they work from both pages and the Hono router.
Deploy target is a Cloudflare Worker, not Cloudflare Pages. The @astrojs/cloudflare adapter is a Workers adapter: it emits dist/server/wrangler.json containing the Worker entry (main), static assets (assets.binding = "ASSETS"), and the D1/R2 bindings from wrangler.toml.
pnpm build
pnpm deploy # wrangler deploy --config dist/server/wrangler.jsonConnect the repo through the Workers Git integration (Workers & Pages → Create → Worker → Connect to Git). Set the build command to pnpm install && pnpm build and the deploy command to wrangler deploy --config dist/server/wrangler.json.
Cloudflare Pages is in maintenance and the @astrojs/cloudflare adapter no longer targets it. If you add pages_build_output_dir to wrangler.toml, Cloudflare rejects the generated config with:
The name 'ASSETS' is reserved in Pages projects.
…and additionally forbids the main + pages_build_output_dir combination. Do not set pages_build_output_dir. The ASSETS binding is legitimate and required for Workers static assets.
- Monolithic API: Hono runs embedded in
src/pages/api/[...path].ts(export const ALL). There is no standalone server. The router is mounted at/apiand exposesGET /api/postsandGET /api/posts/:slug. - Binding access:
Astro.locals.runtime.envwas removed in Astro 6+. Useimport { env } from 'cloudflare:workers'. Thecloudflare:workersmodule has no shipped types, sosrc/env.d.tsdeclares it. Runpnpm cf-typegenafter changingwrangler.toml. - Zero client JS by default: pages are static
.astrocomponents. Preact islands (.tsxundersrc/components/islands/) are used only for genuine interactivity. The only intentional client script is Astro's View Transitions (<ClientRouter />). - Theme switching: Catppuccin Macchiato (dark, default) and Latte (light) are applied via CSS custom properties and
prefers-color-scheme— no JavaScript is required to switch. - TypeScript:
strict: true, noany. TypeScript is pinned to^6(the@astrojs/checkpeer ceiling; do not bump to 7).package.jsondeclarespnpm.onlyBuiltDependencies: [esbuild, workerd]— required because pnpm 10 blocks build scripts by default.
- No tracking, ever: no analytics, cookies, or consent banners.
- pnpm only (not npm/yarn).
- Fonts must keep
subsets: ['latin-ext'](Turkish characters). Self-hosted via@fontsource; import the latin-ext subset CSS specifically.
See DESIGN.md for the full system and visual specification (Poetic Ink × Catppuccin Macchiato): color tokens, typography pairings, the guestbook identity / geometric identicon system, the admin panel & auth model, the R2 media bucket layout, and the future-proof schema. Do not duplicate its contents elsewhere — reference it.
Decisions, proposals, and release history live under docs/:
| Path | Purpose |
|---|---|
docs/adr/ |
Architecture Decision Records — immutable history of decisions (Michael Nygard format). |
docs/plans/ |
RFCs / plans — proposals with a lifecycle (Draft → Proposed → Accepted → Implemented). |
docs/changelog/CHANGELOG.md |
Release notes (Keep a Changelog + SemVer). |
Two opencode skills scaffold and maintain these artifacts: .opencode/skills/docs-generator/ (ADR/RFC/CHANGELOG templates) and .opencode/skills/commit-message/ (Conventional Commits). All documentation is in English.