A personal portfolio website built with the Next.js 16 App Router API on vinext and deployed to Cloudflare Workers.
This repository is operated through vinext. Use the package scripts below for development, verification, and deployment instead of next dev, next build, or next start.
- Framework: vinext with the Next.js 16 App Router API
- Build Tool: Vite
- Language: TypeScript
- Styling: Vanilla CSS with CSS Custom Properties
- Content: MDX
- Database: Cloudflare D1
- Storage: Cloudflare R2
- Deployment: Cloudflare Workers (via
vinextand@cloudflare/vite-plugin)
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
# Run vinext-specific verification
pnpm exec vinext check
# Run the full unit test suite
pnpm exec vitest run
# Type-check without emitting files
pnpm exec tsc --noEmit --pretty false
# Start the local production server
pnpm preview
# Deploy to Cloudflare Workers
pnpm deploysrc/
├── app/ # Next.js App Router pages and layouts
├── lib/
│ ├── components/ # React components
│ ├── content/ # MDX project content
│ ├── data/ # Static data and constants
│ ├── hooks/ # Custom React hooks
│ ├── server/ # Server-side utilities (D1, R2)
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Shared utility functions
└── proxy.ts # Request proxy
- Runtime-specific imports such as
cloudflare:workersare part of the supported production path. src/lib/server/cloudflare-workers.mock.tsexists only for Vitest aliases and is not a fallback for a plainnext buildworkflow.- For production-shape validation, prefer
pnpm build,pnpm exec vinext check, andpnpm exec wrangler deploy --dry-run. - The
developdeploy environment is intentionally read-only: admin writes stay disabled even if the app is pointed at production-backed bindings. - The printable resume is served from
/resume; production also mapsresume.junwon.devto the same Worker via a Cloudflare Workers custom domain route.
The private /a surface includes:
- visitor analytics from the public
/api/analytics/trackbeacon endpoint, - application-specific short URL creation and deletion,
- link-filtered metrics for sessions, page views, dwell time, scroll depth, referrers, countries, and top pages,
- authenticated tools for analytics metrics and application links.
Portfolio content is published from repository MDX and typed TypeScript catalog data; /a does not provide content editing.
See Admin Analytics and Short Links for the data model, auth flow, D1 tables, Cloudflare bindings, and verification commands.
Cloudflare Access should protect only the private admin surface:
/a/a/*/api/admin/*
Keep / public. After Access authenticates /a, the app verifies Cf-Access-Jwt-Assertion and issues a signed admin_session cookie for the retained analytics and application-link tools.
Required Worker settings:
CF_ACCESS_TEAM_DOMAIN:https://<team-name>.cloudflareaccess.comCF_ACCESS_AUD: the Access application audience tag. Use a comma-separated list only during AUD rotation.ADMIN_SESSION_SECRET: set withpnpm exec wrangler secret put ADMIN_SESSION_SECRET; do not store this value in the repository.
Private