A lightweight content management system built on Cloudflare Workers, using D1 for data storage, KV for caching, and R2 for media storage.
-
Internationalization (i18n) Management
- Multi-language support with fallback to default language
- Inline editing of translations
- Section-based organization
- Cached API endpoints for performance
-
Media Management
- Upload files to R2 storage
- Automatic filename sanitization (kebab-case)
- Section-based organization
- Direct streaming from R2
-
Authentication
- Email/password authentication using Better Auth
- Session management with D1 storage
- Protected routes
- Framework: React Router v7
- UI: Tailwind CSS + shadcn/ui components
- Database: Cloudflare D1 (SQLite)
- Cache: Cloudflare KV
- Storage: Cloudflare R2
- Authentication: Better Auth
- Runtime: Cloudflare Workers
npm installCreate a .dev.vars file for local development:
AUTH_SECRET=your-secret-key-here
ADMIN_SIGNUP_PASSWORD=your-admin-signup-secretFor production, set these as Cloudflare secrets:
npx wrangler secret put AUTH_SECRET
npx wrangler secret put ADMIN_SIGNUP_PASSWORDApply the database migrations:
npx wrangler d1 migrations apply edgecms-db --localFor production:
npx wrangler d1 migrations apply edgecms-dbGenerate TypeScript types:
npm run typecheckStart the development server:
npm run devDeploy to Cloudflare Workers:
npm run deploy/edge-cms/sign-in- Authentication page/edge-cms/i18n- Translation management interface/edge-cms/media- Media upload and management
/edge-cms/public/i18n/:locale.json- Get translations for a locale (cached)/edge-cms/public/media/:filename- Serve media files from R2
- Sign in at
/edge-cms/sign-in - Navigate to
/edge-cms/i18n - Add languages and sections as needed
- Add translation keys
- Edit translations inline - changes save automatically
Fetch translations from your application:
const response = await fetch('/edge-cms/public/i18n/en.json');
const translations = await response.json();- Navigate to
/edge-cms/media - Upload files using the upload button
- Files are automatically renamed to kebab-case
- Assign files to sections for organization
Reference media files directly:
<img src="/edge-cms/public/media/my-image.jpg" alt="My Image" />locale- Language code (e.g., 'en', 'es')default- Whether this is the default/fallback language
name- Section identifier for grouping content
key- Translation keylanguage- Language codevalue- Translated textsection- Optional section reference
filename- Sanitized filenamemimeType- File MIME typesizeBytes- File sizesection- Optional section reference
This CMS is designed to be deployed alongside your existing Cloudflare Workers
application. Simply configure the /edge-cms routes in your wrangler
configuration to serve this application on your domain.