This document is the agent-facing map for the private admin surface, visitor analytics, application short links, and the metrics dashboard.
/a: private admin dashboard. It renders analytics and link-management tabs throughsrc/app/a/_components/AdminDashboard.tsxandsrc/app/a/_components/DashboardClient.tsx./a/actions.ts: Server Actions for local dev login, logout, short-link creation, and short-link deletion./:slug: public short URL route rewritten internally tosrc/app/(portfolio)/[locale]/[slug]/page.tsx. It loads active rows fromapplication_links, applies tailored homepage presets, and returns 404 for expired or reserved slugs./api/analytics/track: public analytics Route Handler. It receives browser beacons fromAnalyticsTracker, validates/clamps payloads, and writes to D1.
Admin authorization is centralized in src/lib/server/admin/request.ts.
- Production requests must pass Cloudflare Access JWT verification from
src/lib/server/admin/access.ts. - After a valid Access request, the app can issue a signed
admin_sessioncookie throughsrc/lib/server/admin/session.ts. - Local development can use the
/alogin action, which sets legacyis_adminandowner_devicecookies only outside production. owner_devicealso suppresses analytics collection so owner/admin browsing does not pollute public metrics.- Admin writes must pass both
isCurrentRequestAdmin()andisAdminWriteEnabledForCurrentRuntime(). APP_ENV=developdeploys are intentionally read-only in production runtime, even when they point at production-backed D1/R2 bindings.
Required Worker settings for production admin access:
CF_ACCESS_TEAM_DOMAIN:https://<team-name>.cloudflareaccess.comCF_ACCESS_AUD: Cloudflare Access audience tag. Comma-separated values are supported for AUD rotation.ADMIN_SESSION_SECRET: HMAC secret foradmin_session. Set withpnpm exec wrangler secret put ADMIN_SESSION_SECRET.
schema.sql defines the operational tables:
user_sessions: one row per browser session. Stores country, user agent, referrer, admin flag, and creation time.page_views: one durable row per browser page view whenclient_page_view_idis available. Stores total dwell time, visible-tab active time, document scroll depth, project-article progress, and the farthest visible project section. Linked touser_sessions.application_links: active or expired short links. Stores slug, company/label, role preset, summary preset, ordered project ids, and expiry.application_link_visits: maps one session to one application link. Current v1 attribution is session-scoped, not multi-touch.web_vitals: Core Web Vitals samples reported by Next.jsuseReportWebVitals.tagsandrevalidations: vinext cache support tables.
src/components/analytics/AnalyticsTracker.tsxcreates a session id insessionStorage.- It sends an initial beacon with referrer/user agent and page flush beacons with path, dwell time, active time, document scroll depth, project-article progress, and farthest visible section.
- Single-segment public paths such as
/abcdare interpreted as application-link slugs bysrc/lib/utils/applicationSlug.ts. /api/analytics/trackbypasses local, owner-device, andIGNORE_IPStraffic.src/components/analytics/WebVitalsTracker.tsxreports Next.js Web Vitals to the same endpoint.src/lib/server/analytics/payload.tsvalidates payload shape, clamps dwell/active/scroll/article-progress values, and normalizes optional slugs.src/lib/server/analytics/tracking.tswritesuser_sessions,application_link_visits,page_views, andweb_vitals.
Payload rules:
sessionIdis required and capped at 128 characters.pageViewIdis optional but enables upsert-style page updates instead of duplicate flush rows.pathmust start with/and is capped at 2048 characters.dwellTimeis clamped to0..86400seconds.activeTimeis clamped to0..86400seconds and only counts visible-tab time.scrollDepthis clamped to0..100.articleProgressis clamped to0..100and is measured against.project-article, not the full document.maxVisibleSectionIdandmaxVisibleSectionLabelcapture the farthest reached projecth2.- Web Vital payloads require
metricId,metricName,metricValue, andmetricDelta; rating is normalized togood,needs-improvement,poor, orunknown. referreranduserAgentare trimmed with safe fallbacks.- Explicit
applicationSlugwins; otherwise the server can infer it from a valid single-segment path.
The server performs a lightweight analytics schema bootstrap before writes. Existing D1 databases get the newer page_views columns, client_page_view_id unique index, and web_vitals table without requiring a full table rebuild.
createApplicationLink() accepts:
companyName: required in the UI and stored up to 120 characters.label: optional display label, falling back to the company name.slug: optional custom slug. Invalid characters are removed bynormalizeApplicationSlug().positioning: maps toroleandsummary_preset.ttlDays: clamped to1..90; default UI value is 60 days.projectIds: up to four ordered project ids from the admin UI.
Reserved slugs include a, admin, api, projects, asset paths, and social redirect paths. If a submitted slug is empty or reserved, the server generates a four-character slug from the safe alphabet.
The public short URL page:
- rejects reserved slugs before D1 lookup,
- fetches only active rows where
expires_at > datetime('now'), - applies
project_idsto the featured project order, - applies
summary_presetto homepage introduction copy.
src/app/a/_components/AdminDashboard.tsx reads D1 directly and sends prepared values to the client dashboard.
Global and link-filtered metrics include:
- total sessions,
- total page views,
- average dwell time,
- average scroll depth,
- average visible-tab active time,
- average project-article progress,
- daily range charts for 7 and 30 days,
- monthly range chart for 1 year,
- top pages,
- top referrers,
- top countries,
- Core Web Vitals samples and rating counts,
- active application-link table with sessions, views, average dwell, average active time, average article progress, last seen, and expiry.
Admin sessions are excluded with user_sessions.is_admin = 0.
The production Worker expects these bindings:
VINEXT_KV_CACHE: vinext data cache.portfolio_db: D1 database for analytics, short links, and vinext cache support tables.portfolio_assets: R2 bucket for private asset responses.IMAGES: Cloudflare Images binding used byworker/index.tsfor Next image optimization.ASSETS: static asset binding.APP_ENV: environment marker.
The develop environment intentionally keeps admin writes disabled unless running outside production runtime with ALLOW_ADMIN_WRITES=true.
Use these checks after admin analytics, short-link, or docs changes:
pnpm lint
pnpm exec vitest run
pnpm exec tsc --noEmit --pretty false
pnpm build
pnpm exec vinext check
pnpm exec wrangler deploy --dry-run --outdir /private/tmp/portfolio-worker-dry-run-admin-doc-reviewFor UI smoke tests:
- Run
pnpm dev. - Open
/a. - In local development, use the local login button.
- Check the analytics tab, links tab, range controls, link filter, and generated short URL load path.
- For local analytics write verification, avoid
localhostand127.0.0.1because they are intentionally bypassed. Usehttp://[::1]:3000/...or direct POST requests to the same host. - If D1 writes are unavailable locally, rely on unit tests for attribution and only smoke-test UI load/disabled states.