BridgeWorks Apps is the Next.js frontend for BridgeWorks, a private talent liquidity network that helps people move from a new connection to trusted collaboration and longer-term work.
The backend lives in DoMinhHHung/bridgeworks. Public application requests go through Apache APISIX rather than directly to individual services.
- Next.js App Router and React Server Components
- TypeScript strict mode
- Tailwind CSS and owned shadcn/ui primitives
- Clerk for authentication and session management
- Zod for external-response validation
- Storybook and Vitest
- Playwright and axe accessibility checks
- pnpm and Node.js 24 LTS
As of 2026-08-07, the frontend is deployed on Vercel and the authenticated Identity vertical slice has been exercised against the deployed BridgeWorks backend.
Production frontend: https://bridge-works-apps.vercel.app
Public backend edge: https://apisix-gateway-fi2xhi6azq-as.a.run.app
The deployed request path is:
Browser
-> Vercel / BridgeWorks Apps
-> Clerk session
-> Next.js server-side current-user request
-> public APISIX Cloud Run edge
-> private Identity Cloud Run service
-> PostgreSQL projection
The production integration smoke completed successfully with a real Clerk development user: sign-in succeeded, /app rendered, and the application displayed the real public BridgeWorks ID, primary email, active lifecycle state, joined date, and last Identity update date returned by GET /api/v1/me.
Vercel environment variables are managed outside the repository. The current pre-production integration uses a Clerk development instance; no Clerk Secret Key, session token, webhook secret, or backend credential belongs in Git history, screenshots, logs, fixtures, or client-visible output.
pnpm install --frozen-lockfile
cp .env.example .env.local
pnpm devOpen http://localhost:3000.
Populate .env.local with development values from the Clerk dashboard. Never commit .env.local, real Clerk keys, session tokens, or backend secrets.
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:9080
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_replace_me
CLERK_SECRET_KEY=sk_test_replace_meNEXT_PUBLIC_API_BASE_URL is the project-standard APISIX gateway origin. It is validated as an absolute HTTP or HTTPS origin with no credentials, query, fragment, or path. Missing, placeholder, or malformed values produce a deterministic protected configuration state. The URL is not a credential; authenticated /api/v1/me calls are still initiated only from server code.
The checked-in .env.example contains local origins and safe placeholders only.
src/lib/clerk-config.ts classifies the Clerk key pair as configured, missing, placeholder, or malformed. The Publishable Key is validated using Clerk's documented environment prefix and encoded Frontend API shape. The Secret Key is treated as opaque after its documented environment prefix and is never returned, logged, rendered, snapshotted, or serialized.
src/lib/clerk-config.server.ts, src/lib/auth-session.server.ts, src/lib/bridgeworks-api-config.server.ts, src/lib/bridgeworks-api.server.ts, and the current-user use case import server-only. A Client Component import therefore fails the Next.js build.
The existing fail-closed route policy remains:
| Route | Clerk configured | Clerk unavailable |
|---|---|---|
/ |
Public | Public |
/sign-in/[[...sign-in]] |
Clerk sign-in | Safe configuration state |
/sign-up/[[...sign-up]] |
Clerk sign-up | Safe configuration state |
/app and /app/** |
Authentication required | HTTP 503 fail closed |
/api/** |
No global policy; each future route owns its boundary | No global policy |
The proxy performs the early authentication redirect. Protected layouts and server use cases repeat session checks so a layout is not the only security boundary.
Backend source and tests currently define:
GET /api/v1/me
Authorization: Bearer <Clerk session token>
X-Request-Id: <forwarded or generated request ID>The request goes through APISIX. The frontend does not verify Clerk JWTs and does not send cookies or use credentialed CORS assumptions.
Successful response:
{
"id": "<UUIDv7>",
"id_user": "bw123456789012",
"primary_email": "member@example.com or null",
"status": "active",
"created_at": "<RFC 3339 timestamp>",
"updated_at": "<RFC 3339 timestamp>"
}Error response:
{
"code": "<stable error code>",
"message": "<safe message>",
"request_id": "<request ID>",
"details": null
}Verified mappings:
| HTTP | Code | Frontend state |
|---|---|---|
| 200 | active payload | ready |
| 401 | unauthorized |
Clerk sign-in recovery with /app return URL |
| 403 | account_disabled |
disabled |
| 403 | account_deleted |
deleted |
| 409 | identity_not_ready |
identity not ready; preserves Retry-After |
| 503 | service_unavailable |
service unavailable |
The Identity route does not currently provide a verified stable 429 error code. A structurally valid HTTP 429 response is mapped by status to the rate-limited UI without inventing a backend code. Unsupported status/code combinations and malformed payloads stop at a generic safe state.
Current-user responses are Cache-Control: no-store and vary on Authorization. A 401 includes the backend Bearer challenge; a 409 currently recommends Retry-After: 2.
The current-user vertical slice is separated into:
src/lib/bridgeworks-api-config.ts— pure API origin classification;src/lib/bridgeworks-api-config.server.ts— server-only environment boundary;src/lib/bridgeworks-api-core.ts— bounded request transport with timeout,cache: "no-store",credentials: "omit", Bearer header, request-ID forwarding, and bounded response bodies;src/lib/bridgeworks-api.server.ts— server-only APISIX client entry point;src/features/current-user/current-user-contract.ts— strict Zod decoding and backend status/code mapping;src/features/current-user/current-user.service.server.ts— Clerk token acquisition and typed current-user use case;src/features/current-user/current-user-overview.tsx— product-facing UI state mapping.
The Clerk token exists only in the server request path. It is never returned in a result union, passed to a Client Component, written to logs, or included in Storybook fixtures. Server diagnostics contain only a bounded state name and request ID.
When Identity is ready, /app shows:
- public BridgeWorks ID (
id_user); - primary email or a safe unavailable value;
- active lifecycle state;
- joined date;
- last Identity update date;
- a neutral next-step message without a fake profile route, progress percentage, or product metric.
The internal Identity UUID is validated but not rendered.
Dedicated states exist for loading, Identity synchronization pending, disabled, deleted, unauthorized session recovery, rate limiting, service unavailable, API configuration failure, malformed response, and unexpected failure. Error states do not show stale account data. Backend request IDs are rendered with a clear label on non-success states.
The application shell still exposes only one real route:
Overview → /app
Desktop and mobile navigation consume the same navigation model, and aria-current is derived from the active pathname.
The public landing page contains product-facing value and links to:
- Create an account →
/sign-up - Sign in →
/sign-in
It does not expose framework, testing, engineering-status, fake testimonial, fake company, fake user-count, jobs, marketplace, organization, billing, messaging, or analytics copy.
BridgeWorks uses a focused indigo brand accent plus semantic information, success, warning, and destructive tokens. The existing shadcn/Tailwind foundation, visible focus behavior, reduced-motion support, and restrained card hierarchy remain. Clerk appearance uses the same typography, radius, primary action, input, focus, error, and social-button direction without custom authentication logic.
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm build-storybook
pnpm test:storybook
pnpm test:e2e
pnpm checkpnpm check runs lint, typecheck, unit tests, application build, and Storybook build. Playwright runs Chromium, Firefox, and WebKit.
Secretless CI covers public product copy, auth CTAs, deterministic auth-unavailable pages, fail-closed /app, component states, responsive navigation presentation, accessibility smoke, and horizontal overflow checks at 375, 768, 1024, and 1440 pixels. CI does not bypass Clerk or claim an authenticated Clerk flow without a dedicated test instance and credentials.
Status: Partially completed on the deployed Vercel integration — 2026-08-07. The real Clerk sign-in and real GET /api/v1/me happy path have been verified. Items not explicitly observed remain unchecked.
- A signed-out user opens
/app. - Clerk redirects the user to
/sign-inwith/appas the return destination. - Real Clerk sign-in renders and succeeds.
- The user returns to
/app. - The frontend obtains the Clerk session token only on the server by implementation boundary.
- The server calls APISIX
GET /api/v1/mewith the Bearer token by the verified current-user integration. - A real current-user response renders the public ID, email state, lifecycle, and dates.
- Refreshing
/apppreserves the authenticated session. -
UserButtonopens and works. - Signing out completes successfully.
-
/appbecomes protected again after sign-out. - Browser DevTools and client bundles contain no Clerk Secret Key or session token rendered by the application.
- The APISIX request path does not rely on browser credentialed CORS; the current-user request is server-side.
- Error responses display the backend request ID and honor
Retry-Afterguidance without an automatic retry loop. -
.env.localremains ignored and uncommitted by repository policy.
Agents must read, in order:
AGENTS.md.agents/skills/bridgeworks-ui/SKILL.md.agents/skills/ui-ux-pro-max/SKILL.md
BridgeWorks-specific rules, verified backend contracts, and the existing design system override generic recommendations.
- Do not invent backend endpoints or response fields.
- Keep generic primitives in
src/components/uiand feature code insrc/features. - Prefer Server Components and keep Client Components small.
- Preserve
X-Request-Idand the backend error envelope. - Do not expose tokens, secrets, private provider identifiers, internal UUIDs, or raw backend errors.
- Do not add jobs, talent, marketplace, organizations, billing, messaging, or deployment behavior without an approved vertical slice.