This repository runs a full portfolio experience with:
- Payload CMS for admin/content APIs
- Next.js App Router for the public frontend
- TypeScript across backend (
src/*.ts) and frontend (app/**/*.tsx,components/**/*.tsx,lib/**/*.ts)
This project is now template-friendly. You can reuse it as a starter by setting these values in .env:
SITE_URLSITE_OWNER_NAMESITE_OWNER_ROLESITE_BLOG_LABELCMS_TITLECMS_SUBTITLECMS_MONOGRAM
Then open Payload admin and update:
Globals -> Homepage ContentGlobals -> Admin Branding
This gives you a working white-label portfolio/blog CMS without editing code.
For a release checklist, see TEMPLATE_CHECKLIST.md.
- Payload admin panel at
/admin - Public API access at
/api/* - Next.js frontend pages:
/(portfolio homepage)/blog(notes archive)/blog/[slug](article detail)
- Collections for:
projectsskillsexperiences
- A global document for homepage content:
home
- Auth-enabled
userscollection for admin login
-
Copy environment variables:
cp .env.example .env
-
Install dependencies:
npm install
-
Start MongoDB (local example):
docker run --name portfolio-mongo -p 27017:27017 -d mongo:7
-
Run the app:
npm run dev
-
Open admin panel:
- Use
homefor your hero section, summary, and contact links. - Use
projectsto manage portfolio project cards and details. - Use
skillsfor grouped technical skills and proficiency ratings. - Use
experiencesfor timeline-based work history and impact bullets.
npm run build
npm startIf Railway deploys stall before post-deploy, use:
npm run build:railwayand set:
SKIP_PAYLOAD_ADMIN_BUILD=1This skips the admin prebuild step, which is the most common CI bottleneck.
If you want post-deploy hooks ready for future migrations/warmup, set Railway Post-deploy Command to:
npm run postdeployBy default this is safe/no-op. Enable tasks only when needed via env vars:
POSTDEPLOY_RUN_RICHTEXT_MIGRATION=1
POSTDEPLOY_RUN_WARMUP=1
POSTDEPLOY_WARMUP_URLS=/blog,/open-sourceControl failure behavior:
POSTDEPLOY_FAIL_ON_MIGRATION_ERROR=1
POSTDEPLOY_FAIL_ON_WARMUP_ERROR=0To prevent uploaded images from disappearing or changing across deploys, use a persistent volume for media files:
-
Create and mount a Railway volume (for example at
/data). -
Set:
PAYLOAD_MEDIA_DIR=/data/media PAYLOAD_MEDIA_URL=/media
-
Redeploy.
This project now sanitizes upload filenames to URL-safe ASCII by default, which helps avoid filename encoding issues between environments.
This repo includes an app-layer protection guard for /admin and sensitive /api routes.
- setup + route matrix:
SECURITY_CLOUDFLARE_ACCESS.md - implementation:
src/security/accessControl.ts+src/server.ts - analytics tracking endpoint is served by Express at
/api/blog/analytics(src/server/registerBlogAnalyticsRoute.ts)