Modern portfolio website deployed on Cloudflare Workers with SvelteKit. Features dynamic content from self-hosted Directus CMS, smooth animations, and global CDN delivery.
- Frontend Framework: SvelteKit 2.x with Svelte 5
- Deployment: Cloudflare Workers (via Cloudflare Pages)
- CMS: Self-hosted Directus v11 at
cms.albertshofer.net - Language: TypeScript
- Build Tool: Vite 7
- CI/CD: GitLab CI
/
├── src/
│ ├── lib/
│ │ ├── components/ # Svelte components
│ │ ├── stores/ # State management
│ │ ├── types.ts # TypeScript types
│ │ └── api.ts # API calls to Directus
│ ├── routes/ # SvelteKit routes
│ │ ├── +layout.svelte # Main layout
│ │ ├── +page.svelte # Homepage
│ │ ├── architektur/ # Architecture projects
│ │ ├── fotografie/ # Photography projects
│ │ └── kontakt/ # Contact page
│ ├── app.html # HTML template
│ └── app.css # Global styles
├── static/ # Static assets
├── wrangler.toml # Cloudflare Workers config
├── .gitlab-ci.yml # CI/CD pipeline
└── package.json # Dependencies
- Node.js 20+ and npm
- Cloudflare account (free tier works)
- Wrangler CLI:
npm install -g wrangler - GitLab account (for CI/CD)
npm installCreate .env if you need environment-specific variables:
# Example: Override CMS URL for local testing
PUBLIC_CMS_URL=https://cms.albertshofer.netnpm run devVisit http://localhost:5173
npm run buildOutput will be in .svelte-kit/cloudflare
wrangler loginThis opens a browser to authenticate with your Cloudflare account.
Option A: Via Dashboard
- Go to Cloudflare Dashboard
- Navigate to Workers & Pages
- Create a new Pages project
- Name it
portfolio-albertshofer(matcheswrangler.toml)
Option B: Via CLI (first deployment will auto-create)
npm run deployIn Cloudflare Dashboard under your Pages project:
- Go to "Custom domains"
- Add
albertshofer.net - Add
www.albertshofer.net(optional) - Cloudflare will automatically provision SSL certificates
If you need runtime variables:
wrangler pages secret put CMS_URL
# Enter: https://cms.albertshofer.net# Build and deploy
npm run build
wrangler pages deploy .svelte-kit/cloudflare --project-name=portfolio-albertshoferIn your GitLab project, go to Settings > CI/CD > Variables and add:
-
CLOUDFLARE_API_TOKEN: Your Cloudflare API token- Create at: https://dash.cloudflare.com/profile/api-tokens
- Permissions needed: "Cloudflare Pages - Edit"
- Template: "Edit Cloudflare Workers"
-
CLOUDFLARE_ACCOUNT_ID: Your Cloudflare account ID- Find at: https://dash.cloudflare.com → Workers & Pages → Overview
The pipeline has two stages:
Build Stage (automatic on push):
- Runs
npm ci && npm run build - Creates artifact with built files
- Runs on
mainanddevelopbranches
Deploy Stages (manual trigger):
- Production (
mainbranch): Deploys toalbertshofer.net - Preview (
developbranch): Deploys to preview URL
- Push to
mainordevelop - Wait for build to complete
- Go to GitLab CI/CD > Pipelines
- Click "Play" button on deploy job
- Deployment will start automatically
- Production: https://albertshofer.net
- Preview: https://[branch-name].portfolio-albertshofer.pages.dev
In your Directus .env:
CORS_ENABLED=true
CORS_ORIGIN=https://albertshofer.net,https://www.albertshofer.net,https://*.pages.devEnsure the Public role has read access to:
projectscollection (all fields)directus_files(for images)
- Future-proof: Cloudflare is investing in Workers, not Pages
- Global Performance: Edge computing with <50ms response times
- Zero Infrastructure: No servers, containers, or orchestration
- Free Tier: Unlimited requests and bandwidth
- Automatic Scaling: Handles traffic spikes effortlessly
This setup uses hybrid rendering:
- Static pages are pre-rendered at build time
- Dynamic routes can use SSR if needed
- API routes run on Workers edge
The CMS remains self-hosted because:
- Full control over data and backups
- No vendor lock-in for content
- Directus can be updated independently
- Frontend can be rebuilt without touching CMS
Expected metrics:
- TTFB: <100ms (Cloudflare edge)
- FCP: <1s (optimized bundle)
- LCP: <2s (image optimization)
- CLS: <0.1 (stable layouts)
Check Node.js version:
node --version # Should be 20+Clear cache and reinstall:
rm -rf node_modules .svelte-kit
npm installVerify Wrangler authentication:
wrangler whoamiCheck project name matches wrangler.toml:
cat wrangler.toml | grep name- Check CMS is accessible:
curl https://cms.albertshofer.net - Verify CORS settings in Directus
- Check browser console for CORS errors
- Ensure API permissions are set correctly
- Wait 5-10 minutes after adding custom domain (DNS propagation)
- Check DNS records point to Cloudflare
- Verify SSL certificate status in Cloudflare Dashboard
- Try clearing browser cache
- Make changes locally
- Test with
npm run dev - Commit and push to
developbranch - GitLab CI builds automatically
- Manually trigger preview deployment
- Review at preview URL
- Merge to
mainwhen ready - Manually trigger production deployment
# Start local Cloudflare Workers environment
npm run build
wrangler pages dev .svelte-kit/cloudflareThis simulates the Cloudflare Workers runtime locally.
npm update
npm audit fix- Analytics: Cloudflare Dashboard > Analytics
- Logs:
wrangler pages deployment tail - Performance: Cloudflare Web Analytics (free)
If a deployment has issues:
# List deployments
wrangler pages deployment list --project-name=portfolio-albertshofer
# Rollback to specific deployment
wrangler pages deployment rollback [deployment-id] --project-name=portfolio-albertshoferOr use Cloudflare Dashboard > Deployments > Rollback
Configured in app.html or via Cloudflare Workers:
- Restricts resource loading
- Prevents XSS attacks
- Configured for Directus CMS domain
Never commit secrets! Use:
- GitLab CI/CD Variables (masked)
- Wrangler secrets for runtime
Consider adding rate limiting via Cloudflare:
- Go to Security > WAF
- Set rate limiting rules
- Protect against DDoS
Removed:
- ❌ Nginx configuration
- ❌ Docker containers for frontend
- ❌ Traefik reverse proxy
- ❌ Server maintenance
Kept:
- ✅ Same SvelteKit codebase
- ✅ Same Directus CMS (self-hosted)
- ✅ Same domain names
- ✅ Same content structure
- Cost: $0 for frontend hosting
- Performance: Global CDN vs single server
- Maintenance: No server updates or patches
- Scalability: Unlimited concurrent users
- Reliability: Cloudflare's 100% uptime SLA
- Remote: (Set up your GitLab remote URL)
- Branch:
main(production),develop(staging)
Last Updated: 2025-01-13 Version: 1.0.0 Author: Michael Albertshofer