Skip to content

feat: add protected application shell - #2

Merged
DoMinhHHung merged 45 commits into
mainfrom
feat/authenticated-app-shell
Aug 5, 2026
Merged

feat: add protected application shell#2
DoMinhHHung merged 45 commits into
mainfrom
feat/authenticated-app-shell

Conversation

@DoMinhHHung

@DoMinhHHung DoMinhHHung commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Objective

Add the first production-oriented authenticated application boundary for BridgeWorks Apps without implementing business features or inventing backend APIs.

Final hardening corrections

  • added an enforced server-only boundary to src/lib/clerk-config.server.ts and src/lib/auth-session.server.ts;
  • verified both boundaries with a temporary Client Component: the Next.js production build rejected imports from both server-only modules, then the temporary verification route was removed;
  • replaced unsupported Secret Key payload assumptions with validation limited to Clerk's documented sk_test_ / sk_live_ environment prefixes and a non-empty opaque payload;
  • retained test/live environment mismatch detection;
  • hardened Publishable Key validation around the documented pk_test_ / pk_live_ prefix and base64-encoded Frontend API value with its trailing $ delimiter;
  • added an official-shaped, entirely synthetic test Publishable Key fixture;
  • verified configured, missing, placeholder, malformed, and environment-mismatch states;
  • verified the Secret Key is never returned or present in the serialized classification result;
  • documented the authenticated manual smoke checklist and marked it pending rather than claiming it was executed.

No real credentials were added, logged, rendered, snapshotted, or committed.

What changed

  • centralized Clerk environment classification as configured, missing, placeholder, or malformed;
  • replaced the previous fail-open proxy fallback with an explicit /app/** policy;
  • return a deterministic HTTP 503 response for protected routes when Clerk configuration is unusable;
  • redirect unauthenticated configured requests through Clerk sign-in with a return URL;
  • added dedicated Clerk sign-in and sign-up catch-all routes;
  • redirect already signed-in users from auth pages to /app;
  • perform request-time server session checks in both the protected layout and protected page;
  • added a responsive authenticated shell with one real navigation item: Overview → /app;
  • added a keyboard-accessible mobile sheet, skip link, semantic landmarks, visible focus, reduced motion, and stable loading dimensions;
  • added safe configuration, session-unavailable, loading, and unexpected-error states;
  • added secretless Storybook stories plus unit, Storybook, Playwright, and axe coverage.

Route policy

Route Policy
/ public
/sign-in/[[...sign-in]] public Clerk auth route
/sign-up/[[...sign-up]] public Clerk auth route
/app and /app/** protected, fail closed
/api/** no global policy; each future route must define its own boundary

The proxy is an early redirect/fail-closed boundary. Protected server resources repeat the session check because a layout check alone is not sufficient for every client navigation or future resource.

Server-only build verification

A temporary routable Client Component imported both server modules and intentionally ran the production build.

  • verification run: 30969255622;
  • verification job: 92189821782;
  • expected result: pnpm build failed;
  • confirmed errors:
    • src/lib/auth-session.server.tsserver-only cannot be imported from a Client Component module;
    • src/lib/clerk-config.server.tsserver-only cannot be imported from a Client Component module.

The temporary route was removed before final validation and is not part of the final diff.

Backend contract verification

Backend main confirms:

  • browser authentication uses Authorization: Bearer <Clerk session token>;
  • APISIX forwards the header and does not verify JWT itself;
  • browser CORS is non-credentialed;
  • X-Request-Id is preserved;
  • errors use {code,message,request_id,details}.

This PR does not call the backend and does not add an API client.

Responsive and accessibility behavior

  • 375 px: compact header, 44 px mobile-menu trigger, no horizontal overflow;
  • 768 px: tablet gutters and uncompressed content hierarchy;
  • 1024 px: persistent sidebar navigation;
  • 1440 px: bounded shell and content width;
  • skip link, semantic header/nav/main, aria-current, focus trap, Escape close, focus return, visible focus, reduced-motion support, and no hover-only controls.

Final validation

Final head: 3c1f3e9ab10c57ed504de83a18a7ba7d0fc72fc9

GitHub Actions run 30969358814, job 92190135160, completed successfully on the final clean branch state:

  • pnpm lint — passed;
  • pnpm typecheck — passed;
  • pnpm test — passed, 21 tests;
  • pnpm build — passed;
  • pnpm build-storybook — passed;
  • pnpm test:storybook — passed;
  • pnpm test:e2e — passed, including the configured Chromium, Firefox, and WebKit browser projects.

Authenticated Clerk E2E limitation

Authenticated Clerk E2E is not claimed. CI has no dedicated Clerk test instance or test credentials, so it validates public routes, pure route/configuration policy, presentational shell behavior, deterministic unavailable states, fail-closed protected access, browser checks, and accessibility without weakening production authentication.

Manual authenticated smoke status

Pending — not executed. The README now contains the checklist for signed-out redirect, real Clerk rendering and sign-in, return to /app, session persistence, UserButton, sign-out and renewed protection, .env.local ignore status, and browser secret exposure checks.

Scope exclusions

No jobs, talent, organizations workflow, marketplace, applications, billing, messaging, fake analytics, backend changes, APISIX changes, custom authentication, or production deployment.

Status

  • final head: 3c1f3e9ab10c57ed504de83a18a7ba7d0fc72fc9;
  • draft: yes;
  • ready for review: no;
  • reviewers requested: none;
  • merged: no.

Summary by CodeRabbit

  • Tính năng mới

    • Bổ sung đăng nhập, đăng ký và bảo vệ khu vực ứng dụng.
    • Thêm giao diện ứng dụng responsive với điều hướng desktop/mobile, quản lý tài khoản và trạng thái tải.
    • Bổ sung xử lý lỗi, trạng thái xác thực và chuyển hướng phù hợp.
  • Cải tiến

    • Cập nhật trang chủ, tài liệu cấu hình xác thực và hướng dẫn kiểm thử.
    • Hỗ trợ giảm chuyển động để cải thiện khả năng tiếp cận.
  • Kiểm thử

    • Mở rộng kiểm thử đơn vị, giao diện, accessibility và các luồng xác thực.
    • Bổ sung các kịch bản Storybook cho giao diện ứng dụng.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PR tích hợp xác thực Clerk theo hướng fail-closed. PR thêm các route xác thực, route ứng dụng được bảo vệ, application shell responsive, trạng thái lỗi và kiểm thử unit, Playwright, accessibility.

Changes

Xác thực Clerk và bảo vệ route

Layer / File(s) Summary
Cấu hình, trạng thái phiên và proxy
src/lib/clerk-config.ts, src/lib/clerk-config.server.ts, src/lib/auth-routes.ts, src/lib/auth-session.server.ts, src/proxy.ts, src/app/layout.tsx
Hệ thống phân loại cấu hình Clerk thành các trạng thái rõ ràng. Proxy bảo vệ /app và trả về 503 khi cấu hình chưa sẵn sàng.
Trang đăng nhập và đăng ký
src/app/(auth)/**, src/components/layout/auth-page.tsx, src/components/layout/auth-state.tsx, src/app/layout.tsx
Các trang Clerk xử lý trạng thái cấu hình, trạng thái phiên, chuyển hướng và biểu mẫu đăng nhập hoặc đăng ký.
Ứng dụng được bảo vệ và application shell
src/app/(protected)/app/**, src/components/layout/app-shell.tsx, src/components/layout/mobile-app-navigation.tsx, src/app/globals.css
Khu vực /app hiển thị overview, UserButton, sidebar desktop, navigation mobile, loading state và error boundary. Giao diện hỗ trợ keyboard focus và reduced motion.
Kiểm thử và cấu hình chạy test
src/lib/*.test.ts, src/components/layout/*.test.tsx, vitest.config.ts, vitest.setup.ts, package.json
Vitest thêm project unit với jsdom. Kiểm thử bao phủ cấu hình Clerk, route protection và application shell.
Playwright, CI và tài liệu
tests/example.spec.ts, .github/workflows/playwright.yml, README.md
Playwright kiểm tra các route xác thực, phản hồi 503, không lộ secret và accessibility. README mô tả contract, lệnh test và checklist smoke test.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • DoMinhHHung/BridgeWorksApps#1: PR này mở rộng tích hợp Clerk trước đó trong cùng các file layout, proxy, README, CI và cấu hình test.

Poem

Thỏ nhỏ thấy Clerk mở cửa,
Route /app giữ lối an toàn.
Shell responsive nhảy qua màn,
Test soi focus, lỗi và motion.
Cà rốt mừng CI xanh hơn!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Tiêu đề mô tả rõ thay đổi chính: thêm application shell được bảo vệ bằng xác thực.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/authenticated-app-shell

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DoMinhHHung
DoMinhHHung marked this pull request as ready for review August 5, 2026 03:24
@DoMinhHHung
DoMinhHHung merged commit 3086aec into main Aug 5, 2026
1 of 2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/layout.tsx (1)

40-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Đọc cấu hình Clerk tại thời điểm chạy.

RootLayout hiện đồng bộ và không khai báo dynamic = "force-dynamic". Nếu Next.js render tĩnh layout này, nhánh có/không có ClerkProvider sẽ bị cố định theo biến môi trường tại build. Đặt môi trường production vào NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEY trước build, hoặc thêm force-dynamic để quyết định cấu hình tại mỗi request.

Kiểm tra prop sign-out.

afterSignOutUrl không còn được khuyến nghị và có thể đã deprecated trong @clerk/nextjs 7.6.4. Dùng biến môi trường redirects của Clerk hoặc config redirect trong xử lý sign-out thay vì truyền prop vào ClerkProvider.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/layout.tsx` around lines 40 - 69, Update RootLayout to force
request-time rendering so Clerk configuration is evaluated per request rather
than fixed during build, and remove afterSignOutUrl from ClerkProvider because
it is deprecated; configure sign-out redirection through Clerk environment
settings or the existing sign-out handling instead.
🧹 Nitpick comments (6)
src/components/layout/auth-page.tsx (1)

46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Bỏ một trong hai nhãn trùng lặp.

Phần tử có cả aria-label="Loading authentication form" và văn bản sr-only cùng nội dung. aria-label ghi đè nội dung con, nên văn bản sr-only là dư thừa. Hãy giữ một nguồn nhãn duy nhất.

♻️ Đề xuất thay đổi
     <div
       role="status"
-      aria-label="Loading authentication form"
       className="h-[31rem] w-full max-w-[25rem] animate-pulse rounded-xl border border-border bg-card motion-reduce:animate-none"
     >
       <span className="sr-only">Loading authentication form</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/layout/auth-page.tsx` around lines 46 - 52, Remove one of the
duplicate accessible labels from the loading element in the authentication page:
either remove aria-label or the matching sr-only span, leaving exactly one
accessible name for the status container.
src/app/(protected)/app/page.tsx (1)

26-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Cân nhắc bỏ kiểm tra phiên trùng lặp trong trang.

ProtectedAppLayout đã kiểm tra cấu hình và phiên trước khi render children. Trang lặp lại toàn bộ chuỗi kiểm tra và thêm một lần gọi getClerkSessionState() cho mỗi request. Nếu bạn giữ lớp kiểm tra này có chủ ý, hãy thêm chú thích ngắn nêu rõ đây là phòng vệ theo chiều sâu.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(protected)/app/page.tsx around lines 26 - 45, Remove the duplicated
configuration and session checks from the page around getClerkConfiguration and
getClerkSessionState, relying on ProtectedAppLayout to validate access before
rendering children. Preserve only page-specific rendering logic; if the checks
must remain, add a brief comment identifying them as defense-in-depth.
src/components/layout/app-shell.tsx (1)

45-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

aria-current="page" được gán cứng cho liên kết Overview.

Hiện tại shell chỉ có một mục điều hướng, nên giá trị này đúng. Khi bạn thêm route con dưới /app, liên kết Overview sẽ tiếp tục báo là trang hiện tại trên mọi route. Hãy tính giá trị từ pathname khi thêm mục điều hướng thứ hai.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/layout/app-shell.tsx` around lines 45 - 52, Update the
Overview Link in the app-shell navigation to derive aria-current from the
current pathname instead of hard-coding "page". Use the existing
pathname-routing mechanism, marking it current only for APP_ROUTE while leaving
it unset on child routes and preserving the current single-item behavior.
src/proxy.ts (1)

13-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Sửa thứ tự tiêu đề trong trang 503.

Thẻ <p>BridgeWorks</p> đứng trước <h1>. Nội dung thương hiệu nên nằm trong landmark hoặc sau tiêu đề chính để trình đọc màn hình đọc tiêu đề trang trước. Phần còn lại của phản hồi (no-store, CSP, noindex) là chính xác.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proxy.ts` around lines 13 - 51, Update clerkUnavailableResponse so the
main heading is encountered before the BridgeWorks branding paragraph, placing
the branding after the h1 or within an appropriate landmark. Preserve the
existing 503 response content, no-store cache policy, CSP, and noindex headers.
src/app/(auth)/sign-in/[[...sign-in]]/page.tsx (1)

31-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hai trang xác thực lặp cùng một cấu trúc phân nhánh. Mỗi tệp lặp lại AuthPage bốn lần với cùng titledescription, và cả hai tệp dùng chung trình tự kiểm tra cấu hình rồi kiểm tra phiên. Nguyên nhân gốc là thiếu một thành phần dùng chung cho luồng xác thực.

  • src/app/(auth)/sign-in/[[...sign-in]]/page.tsx#L31-L65: tách titledescription thành hằng số, bọc AuthPage một lần, và chỉ chọn nội dung con theo trạng thái.
  • src/app/(auth)/sign-up/[[...sign-up]]/page.tsx#L31-L65: áp dụng cùng cấu trúc và dùng lại helper chung cho phần kiểm tra cấu hình và phiên.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(auth)/sign-in/[[...sign-in]]/page.tsx around lines 31 - 65, The
authentication pages duplicate the same AuthPage branching and
configuration/session checks. In src/app/(auth)/sign-in/[[...sign-in]]/page.tsx
lines 31-65, extract shared title and description constants, render AuthPage
once, and select only the AuthState child by status; in
src/app/(auth)/sign-up/[[...sign-up]]/page.tsx lines 31-65, apply the same
structure and reuse a shared helper for configuration and session checks.
Preserve the existing redirects and state-specific AuthState values.
src/app/(protected)/app/error.tsx (1)

14-14: 🧹 Nitpick | 🔵 Trivial

Cân nhắc ghi log error.digest để truy vết.

Component nhận error nhưng không dùng. Giao diện đúng khi không hiển thị chi tiết lỗi cho người dùng. Để hỗ trợ vận hành, hãy gửi error.digest tới hệ thống giám sát trong một useEffect. Việc này giúp đối chiếu lỗi phía trình duyệt với log máy chủ.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(protected)/app/error.tsx at line 14, Trong component AppError, nhận
và sử dụng prop error cùng reset, rồi thêm useEffect để gửi error.digest tới hệ
thống giám sát khi lỗi thay đổi. Giữ nguyên giao diện hiện tại, không hiển thị
chi tiết lỗi cho người dùng.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 11: Remove the --passWithNoTests flag from the package.json test script
while preserving the unit project selection, so Vitest fails when the expected
src/**/*.test.{ts,tsx} tests are not discovered.

In `@src/app/`(protected)/app/layout.tsx:
- Around line 53-56: Replace the signed-out handling in
src/app/(protected)/app/layout.tsx#L53-L56 with next/navigation
redirect(SIGN_IN_ROUTE), ensuring the Server Component returns the supported
redirect behavior rather than session.redirectToSignIn(). Apply the same change
to the signed-out branch in src/app/(protected)/app/page.tsx#L37-L39; both sites
should redirect to SIGN_IN_ROUTE.

In `@src/proxy.ts`:
- Around line 61-81: Update the authentication flow inside the configuredProxy
clerkMiddleware callback to use Clerk v7’s App Router auth API rather than
reading session.isAuthenticated from auth(). Protect the request with
auth.protect() and preserve redirectToSignIn with the request URL for
unauthorized access; keep the pathname guard and successful NextResponse.next()
behavior unchanged.

In `@tests/example.spec.ts`:
- Around line 15-57: Update the Playwright webServer configuration used by
“renders deterministic secretless sign-in and sign-up states” and “fails
closed...” so the E2E server explicitly runs with CLERK_SECRET_KEY absent and
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY set to the placeholder value. Prevent
inherited .env.local credentials from affecting these assertions while leaving
the expected missing-configuration and 503 behavior unchanged.

---

Outside diff comments:
In `@src/app/layout.tsx`:
- Around line 40-69: Update RootLayout to force request-time rendering so Clerk
configuration is evaluated per request rather than fixed during build, and
remove afterSignOutUrl from ClerkProvider because it is deprecated; configure
sign-out redirection through Clerk environment settings or the existing sign-out
handling instead.

---

Nitpick comments:
In `@src/app/`(auth)/sign-in/[[...sign-in]]/page.tsx:
- Around line 31-65: The authentication pages duplicate the same AuthPage
branching and configuration/session checks. In
src/app/(auth)/sign-in/[[...sign-in]]/page.tsx lines 31-65, extract shared title
and description constants, render AuthPage once, and select only the AuthState
child by status; in src/app/(auth)/sign-up/[[...sign-up]]/page.tsx lines 31-65,
apply the same structure and reuse a shared helper for configuration and session
checks. Preserve the existing redirects and state-specific AuthState values.

In `@src/app/`(protected)/app/error.tsx:
- Line 14: Trong component AppError, nhận và sử dụng prop error cùng reset, rồi
thêm useEffect để gửi error.digest tới hệ thống giám sát khi lỗi thay đổi. Giữ
nguyên giao diện hiện tại, không hiển thị chi tiết lỗi cho người dùng.

In `@src/app/`(protected)/app/page.tsx:
- Around line 26-45: Remove the duplicated configuration and session checks from
the page around getClerkConfiguration and getClerkSessionState, relying on
ProtectedAppLayout to validate access before rendering children. Preserve only
page-specific rendering logic; if the checks must remain, add a brief comment
identifying them as defense-in-depth.

In `@src/components/layout/app-shell.tsx`:
- Around line 45-52: Update the Overview Link in the app-shell navigation to
derive aria-current from the current pathname instead of hard-coding "page". Use
the existing pathname-routing mechanism, marking it current only for APP_ROUTE
while leaving it unset on child routes and preserving the current single-item
behavior.

In `@src/components/layout/auth-page.tsx`:
- Around line 46-52: Remove one of the duplicate accessible labels from the
loading element in the authentication page: either remove aria-label or the
matching sr-only span, leaving exactly one accessible name for the status
container.

In `@src/proxy.ts`:
- Around line 13-51: Update clerkUnavailableResponse so the main heading is
encountered before the BridgeWorks branding paragraph, placing the branding
after the h1 or within an appropriate landmark. Preserve the existing 503
response content, no-store cache policy, CSP, and noindex headers.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e18ffbc-9f13-4a62-afc6-739ab27857a7

📥 Commits

Reviewing files that changed from the base of the PR and between dd02bd7 and 3c1f3e9.

📒 Files selected for processing (29)
  • .github/workflows/playwright.yml
  • README.md
  • package.json
  • src/app/(auth)/sign-in/[[...sign-in]]/page.tsx
  • src/app/(auth)/sign-up/[[...sign-up]]/page.tsx
  • src/app/(protected)/app/error.tsx
  • src/app/(protected)/app/layout.tsx
  • src/app/(protected)/app/loading.tsx
  • src/app/(protected)/app/page.tsx
  • src/app/globals.css
  • src/app/layout.tsx
  • src/app/page.tsx
  • src/components/layout/app-shell.stories.tsx
  • src/components/layout/app-shell.test.tsx
  • src/components/layout/app-shell.tsx
  • src/components/layout/auth-page.tsx
  • src/components/layout/auth-state.stories.tsx
  • src/components/layout/auth-state.tsx
  • src/components/layout/mobile-app-navigation.tsx
  • src/lib/auth-routes.test.ts
  • src/lib/auth-routes.ts
  • src/lib/auth-session.server.ts
  • src/lib/clerk-config.server.ts
  • src/lib/clerk-config.test.ts
  • src/lib/clerk-config.ts
  • src/proxy.ts
  • tests/example.spec.ts
  • vitest.config.ts
  • vitest.setup.ts
📜 Review details
🧰 Additional context used
🪛 React Doctor (0.9.3)
src/lib/clerk-config.test.ts

[error] 13-13: This hardcoded secret is a security vulnerability: it ships to the browser where anyone can read it.

Move secrets to server-only code. In Next.js, only NEXT_PUBLIC_* env vars are exposed to the browser, and they must not contain secrets

(no-secrets-in-client-code)

🔇 Additional comments (19)
vitest.config.ts (1)

1-45: LGTM!

vitest.setup.ts (1)

1-33: LGTM!

tests/example.spec.ts (1)

4-13: LGTM!

Also applies to: 59-68

.github/workflows/playwright.yml (1)

60-60: LGTM!

src/app/page.tsx (1)

5-6: LGTM!

Also applies to: 15-20, 38-49

src/lib/clerk-config.ts (1)

1-162: LGTM!

src/lib/clerk-config.server.ts (1)

1-17: LGTM!

src/lib/auth-session.server.ts (1)

1-27: LGTM!

src/lib/clerk-config.test.ts (1)

1-109: LGTM!

README.md (1)

38-38: LGTM!

Also applies to: 47-72, 74-98, 106-140, 155-155, 168-171

src/components/layout/auth-state.stories.tsx (1)

1-37: LGTM!

src/app/(protected)/app/loading.tsx (1)

1-17: LGTM!

src/components/layout/mobile-app-navigation.tsx (1)

1-90: LGTM!

src/app/globals.css (1)

132-141: LGTM!

src/components/layout/app-shell.test.tsx (1)

1-70: LGTM!

src/components/layout/app-shell.stories.tsx (1)

1-92: LGTM!

src/lib/auth-routes.ts (1)

1-14: LGTM!

src/lib/auth-routes.test.ts (1)

1-24: LGTM!

src/components/layout/auth-state.tsx (1)

60-95: LGTM!

Comment thread package.json
"lint": "eslint",
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests",
"test": "vitest run --project=unit --passWithNoTests",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Không che giấu lỗi không phát hiện kiểm thử.

Repository đã có unit test trong src/. --passWithNoTests làm bước CI thành công nếu glob src/**/*.test.{ts,tsx} bị sai hoặc không còn tìm thấy kiểm thử. Hãy để Vitest thất bại trong trường hợp đó.

Đề xuất sửa
-    "test": "vitest run --project=unit --passWithNoTests",
+    "test": "vitest run --project=unit",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"test": "vitest run --project=unit --passWithNoTests",
"test": "vitest run --project=unit",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 11, Remove the --passWithNoTests flag from the
package.json test script while preserving the unit project selection, so Vitest
fails when the expected src/**/*.test.{ts,tsx} tests are not discovered.

Comment on lines +53 to +56
const session = await getClerkSessionState();
if (session.status === "signed-out") {
return session.redirectToSignIn({ returnBackUrl: APP_ROUTE });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Mô tả: Kiểm tra hợp đồng của getClerkSessionState và redirectToSignIn.
fd -t f 'auth-session' src | xargs -r cat -n

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant files:"
fd -t f 'layout.tsx|page.tsx' 'src/app' | sort

echo
echo "layout.tsx:"
cat -n 'src/app/(protected)/app/layout.tsx'

echo
echo "page.tsx:"
cat -n 'src/app/(protected)/app/page.tsx'

echo
echo "All redirectToSignIn return usages:"
rg -n "redirectToSignIn|signed-out" 'src/app'

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 7455


🌐 Web query:

Clerk Next.js auth redirectToSignIn Server Components Response middleware Route Handler

💡 Result:

In Clerk's Next.js SDK, the recommended approach for handling authentication and redirects varies based on the context (Server Components, Middleware, or Route Handlers). Server Components In Server Components, use the auth helper from @clerk/nextjs/server to access authentication state and redirection methods [1][2]. - Use auth.redirectToSignIn to explicitly redirect unauthenticated users to your sign-in page [3][1]. - Alternatively, use await auth.protect, which automatically redirects unauthenticated users to the sign-in page for document requests (pages) [3][4]. Middleware In your middleware.ts, use the clerkMiddleware function [5][6]. - You can protect routes by using auth.protect inside the middleware callback [4][6]. - If you need custom redirect logic (e.g., conditional redirects based on complex logic), you can manually check the user ID and return a NextResponse.redirect [7]. Route Handlers and Server Actions For non-document requests like Route Handlers or Server Actions, auth.protect does not redirect to the sign-in page; instead, it throws a 401 error or a 404/unauthorized error, as these are typically API or background requests [3][4]. - To handle unauthenticated states in these contexts, use auth to check isAuthenticated and implement your own response (e.g., return a JSON response with a 401 status) [3][2][8]. Key Considerations - clerkMiddleware: This is required to be configured for auth and related helpers to function [5][1]. - Async Requirements: Since version 6, auth.protect is an asynchronous method and must be awaited [7]. - Dynamic Rendering: Using auth in a Server Component will opt that route into dynamic rendering [9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Next/Clerk package versions:"
if [ -f package.json ]; then
  node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
console.log(JSON.stringify({
  dependencies: {
    next: pkg.dependencies?.next,
    "`@clerk/nextjs`": pkg.dependencies?.["`@clerk/nextjs`"],
  },
  devDependencies: {
    "`@clerk/nextjs`": pkg.devDependencies?.["`@clerk/nextjs`"],
  }
}, null, 2));
JS
fi

echo
echo "Package lock snippets:"
fd -t f 'package-lock.json|pnpm-lock.yaml|yarn.lock|bun.lockb' . -d 1 | while read -r f; do
  [ -f "$f" ] && { echo "--- $f"; rg -n '(`@clerk/nextjs`|next@|next:|next)?:?' "$f" | head -40; }
done

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 1971


Không trả về redirectToSignIn() trực tiếp từ Server Component. getClerkSessionState() trả về đúng session.redirectToSignIn, và session.redirectToSignIn(...) ở đây trả về Response, không phải React node. Đổi cả hai nhánh signed-out thành redirect(SIGN_IN_ROUTE) của next/navigation, hoặc dùng ClerkServer API trả về dấu để render ClerkProvider.

  • src/app/(protected)/app/layout.tsx#L53-L56: thay bằng redirect(SIGN_IN_ROUTE) hoặc API Clerk Server Component hỗ trợ chuyển hướng.
  • src/app/(protected)/app/page.tsx#L37-L39: áp dụng cùng cách xử lý cho nhánh signed-out.
📍 Affects 2 files
  • src/app/(protected)/app/layout.tsx#L53-L56 (this comment)
  • src/app/(protected)/app/page.tsx#L37-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(protected)/app/layout.tsx around lines 53 - 56, Replace the
signed-out handling in src/app/(protected)/app/layout.tsx#L53-L56 with
next/navigation redirect(SIGN_IN_ROUTE), ensuring the Server Component returns
the supported redirect behavior rather than session.redirectToSignIn(). Apply
the same change to the signed-out branch in
src/app/(protected)/app/page.tsx#L37-L39; both sites should redirect to
SIGN_IN_ROUTE.

Comment thread src/proxy.ts
Comment on lines +61 to +81
const configuredProxy =
clerkConfiguration.status === "configured"
? clerkMiddleware(
async (auth, request) => {
if (!isProtectedAppPathname(request.nextUrl.pathname)) {
return NextResponse.next();
}

const session = await auth();
if (!session.isAuthenticated) {
return session.redirectToSignIn({ returnBackUrl: request.url });
}

return NextResponse.next();
},
{
signInUrl: SIGN_IN_ROUTE,
signUpUrl: SIGN_UP_ROUTE,
},
)
: unavailableProxy;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

@clerk/nextjs v7 clerkMiddleware auth() isAuthenticated property

💡 Result:

In @clerk/nextjs v7, the auth() helper returns an object that includes the isAuthenticated property [1][2]. This property is a boolean that you can use to check if a user is signed in, effectively replacing the older !!userId pattern [3][2]. Key points regarding auth() in v7: - Asynchronous: You must await auth() to access its properties [3][4]. - Usage: It is designed for use in the App Router, specifically within Server Components, Route Handlers, and Server Actions [1][2]. - protect() vs isAuthenticated: While isAuthenticated allows you to handle unauthenticated states manually (e.g., returning a custom 401 response or redirecting) [2], the auth.protect() method is the recommended way to enforce protection automatically [1][5][6]. - clerkMiddleware(): clerkMiddleware() must be configured for auth() to function correctly [1][7], but auth() is the helper used to retrieve the authentication state, not clerkMiddleware() directly [7]. Example usage: import { auth } from '@clerk/nextjs/server' export default async function Page { const { isAuthenticated, userId } = await auth if (!isAuthenticated) { return

Not signed in
} return
Hello, {userId}
}

Citations:


🌐 Web query:

@clerk/nextjs 7.6.4 ClerkMiddlewareRequestHandler authenticateRequest returnBackUrl

💡 Result:

In @clerk/nextjs version 7.6.4, the ClerkMiddlewareRequestHandler (the function passed to clerkMiddleware) provides an auth() helper [1][2]. This auth() helper includes the redirectToSignIn() method, which accepts an optional returnBackUrl parameter [2][3]. To use this within your middleware: 1. The auth() helper is returned by the middleware handler [2]. 2. You can call auth().redirectToSignIn({ returnBackUrl: 'your-url' }) [3]. When you invoke auth().redirectToSignIn({ returnBackUrl }), Clerk handles the redirection logic to the configured sign-in URL, appending the specified returnBackUrl so that the user can be redirected back to the desired page after authentication [2][4][3]. Regarding authenticateRequest: This is a low-level method found on the clerkClient object (accessible via await clerkClient() in Server Components or Route Handlers), not a direct parameter within the clerkMiddleware handler [5][6]. It is used to verify session tokens directly rather than managing redirect flows within middleware [5]. If you are looking to protect routes or manage redirects within clerkMiddleware, the auth().protect() or auth().redirectToSignIn() methods are the intended high-level patterns [4][3]. Note that if you use auth().protect(), it internally handles redirects and, in many cases, automatically manages the returnBackUrl to redirect the user back to the current page after they sign in [4][3]. If you encounter issues where the returnBackUrl is resolving incorrectly (e.g., to an empty string or current URL in specific environments), ensure your environment variables like NEXT_PUBLIC_CLERK_SIGN_IN_URL are correctly configured, as Clerk relies on these to resolve the destination [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
tar -xzf "$tarball"

echo '--- auth.d.ts relevant ---'
grep -n -A 8 -B 2 'isAuthenticated' node_modules/@clerk/nextjs/dist/types/server/auth.d.ts || true
echo '--- auth runtime relevant ---'
grep -n -A 12 -B 5 'isAuthenticated' node_modules/@clerk/nextjs/dist/cjs/server/auth.js || true

echo '--- clerkMiddleware.d.ts relevant ---'
grep -n -A 12 -B 4 'clerkMiddleware' node_modules/@clerk/nextjs/dist/types/server/clerkMiddleware.d.ts || true

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 694


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg

echo '--- package structure ---'
find pkg -maxdepth 3 -type f | sort

echo '--- auth.d.ts relevant ---'
find pkg -name auth.d.ts -print | while read -r f; do
  echo "FILE: $f"
  grep -n -A 8 -B 2 'isAuthenticated' "$f" || true
done

echo '--- auth runtime relevant ---'
find pkg -path '*.js' | while read -r f; do
  case "$f" in
    */auth.js|*/auth.cjs|*auth*runtime*|*getAuth*|*MiddlewareRequestHandler*)
      echo "FILE: $f"
      grep -n -A 12 -B 5 'isAuthenticated' "$f" || true
      ;;
  esac
done

echo '--- clerkMiddleware type/runtime relevant ---'
find pkg -type f \( -name '*middleware*' -o -name '*Middleware*' -o -name '*proxy*' \) -path '*.d.ts' -path '*.js' -print | while read -r f; do
  echo "FILE: $f"
  grep -n -A 12 -B 5 'clerkMiddleware\|isAuthenticated\|NextResponse\.next' "$f" || true
done

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 1815


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/nextjs`@7.6.4 --silent
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg

echo '--- package files around auth.js ---'
find pkg -name auth.js -o -name auth.d.ts -o -name getAuth.d.ts -o -name clerkMiddleware.d.ts | sort

echo '--- app-router auth.d.ts ---'
file="$(find pkg -name auth.d.ts | head -n 1)"
if [ -n "${file:-}" ]; then
  sed -n '1,220p' "$file"
fi

echo '--- app-router auth ES source ---'
file="$(find pkg -path '*/dist/esm/app-router/server/auth.js' | head -n 1)"
if [ -n "${file:-}" ]; then
  sed -n '1,220p' "$file"
fi

echo '--- package files matching middleware/proxy ---'
find pkg -type f \( -iname '*middleware*' -o -iname '*proxy*' \) | sort

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 9996


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg

echo '--- clerkMiddleware type relevant lines ---'
sed -n '1,220p' pkg/package/dist/types/server/clerkMiddleware.d.ts | grep -n -A 8 -B 8 'ClerkMiddlewareRequestHandler\|clerkMiddleware\|auth\|ClerkProxyResponse' || true

echo '--- app-router server auth.d.ts SessionAuthWithRedirect ---'
sed -n '1,80p' pkg/package/dist/types/app-router/server/auth.d.ts

echo '--- backend SessionAuthObject isAuthenticated in `@clerk/backend` ---'
npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt
tarball2="$(cat /tmp/backend_pkg.txt)"
mkdir pkgbe
tar -xzf "$tarball2" -C pkgbe
find pkgbe -name '*auth*' -name '*.d.ts' | sort
while IFS= read -r f; do
  echo "FILE: $f"
  grep -n -A 8 -B 3 'SessionAuthObject\|isAuthenticated' "$f" || true
done < <(find pkgbe -name '*auth*' -name '*.d.ts' | sort)

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 7835


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt

tarball="$(cat /tmp/nextjs764_pkg.txt)"
tarball2="$(cat /tmp/backend_pkg.txt)"

mkdir pkg pkgbe
tar -xzf "$tarball" -C pkg
tar -xzf "$tarball2" -C pkgbe

echo '--- pkgbe auth object relevant files ---'
for f in pkgbe/package/dist/tokens/authObjects.d.ts pkgbe/package/dist/tokens/authStatus.d.ts pkgbe/package/dist/tokens/authenticateContext.d.ts; do
  if [ -f "$f" ]; then
    echo "### $f"
    grep -n -A 12 -B 4 'isAuthenticated\|SignedInAuthObject\|SignedOutAuthObject\|SessionAuthObject'\* "$f" || true
  fi
done

echo '--- pkg nextjs imports SessionAuthObject ---'
grep -R -n "SessionAuthObject" pkg/package/dist/types || true

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 5476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
cd "$tmp"

npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt
tarball="$(cat /tmp/backend_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg

echo '--- all SessionAuthObject definitions/references ---'
grep -R -n "SessionAuthObject" pkg/package/dist || true

echo '--- all isAuthenticated definitions/references ---'
grep -R -n "isAuthenticated" pkg/package/dist || true

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 432


Sử dụng thuộc tính xác thực phù hợp với API auth() Clerk v7.

clerkMiddleware v7 truyền auth() là type App Router (auth.protect) chứ không truyền SignedInAuthObject | SignedOutAuthObject. Nếu auth() phải trả về auth object middleware, hãy xác định đúng type; còn nếu chỉ dùng redirectToSignIn, bỏ check session.isAuthenticated và gọi redirectToSignIn() khi auth.protect() lỗi/unauthorized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proxy.ts` around lines 61 - 81, Update the authentication flow inside the
configuredProxy clerkMiddleware callback to use Clerk v7’s App Router auth API
rather than reading session.isAuthenticated from auth(). Protect the request
with auth.protect() and preserve redirectToSignIn with the request URL for
unauthorized access; keep the pathname guard and successful NextResponse.next()
behavior unchanged.

Comment thread tests/example.spec.ts
Comment on lines +15 to +57
test("renders deterministic secretless sign-in and sign-up states", async ({
page,
}) => {
await page.goto("/sign-in");
await expect(
page.getByRole("heading", { name: "Sign in to BridgeWorks", level: 1 }),
).toBeVisible();
await expect(
page.getByRole("heading", {
name: /Authentication (is not configured|setup is incomplete|configuration is invalid)/,
level: 2,
}),
).toBeVisible();

await page.goto("/sign-up");
await expect(
page.getByRole("link", { name: "Explore the product foundation" }),
page.getByRole("heading", {
name: "Create your BridgeWorks account",
level: 1,
}),
).toBeVisible();
await expect(
page.getByRole("heading", {
name: /Authentication (is not configured|setup is incomplete|configuration is invalid)/,
level: 2,
}),
).toBeVisible();
});

test("has no automatically detectable accessibility violations", async ({
test("fails closed when a protected route has no usable Clerk configuration", async ({
page,
}) => {
await page.goto("/");
const response = await page.goto("/app");

const results = await new AxeBuilder({ page }).analyze();
expect(response?.status()).toBe(503);
await expect(
page.getByRole("heading", {
name: "Authentication is temporarily unavailable",
level: 1,
}),
).toBeVisible();
await expect(page.getByText(/sk_test|CLERK_SECRET_KEY/)).toHaveCount(0);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Tìm cấu hình Playwright và lệnh khởi động E2E.
fd -HI '^playwright\.config\.' .
rg -n -C 6 '"test:e2e"|playwright|webServer|CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY' \
  package.json .github tests \
  $(fd -HI '^playwright\.config\.' .)

# Kỳ vọng: web server E2E xóa hoặc thay thế rõ ràng cả hai biến Clerk.

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 5990


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== env files =="
git ls-files | rg '(^|/)\.env|(^|/)app/|CLERK|CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK|sign-in|sign-up|clerk|middleware' || true

echo
echo "== playwright config =="
cat -n playwright.config.ts

echo
echo "== example test =="
cat -n tests/example.spec.ts

echo
echo "== focused Clerk environment usage =="
rg -n -C 3 'CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY|Clerk|sign-in|sign-up|middleware' . \
  -g '!node_modules' -g '!dist' -g '!build' -g '!playwright-report' -g '!test-results' || true

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 40597


🌐 Web query:

Clerk Next.js ClerkProvider publishableKey required when server-side auth and middleware configured

💡 Result:

In Clerk for Next.js, you generally do not need to pass the publishableKey as a prop to the ClerkProvider component if it is correctly set in your environment variables [1][2]. The ClerkProvider and clerkMiddleware helpers are designed to automatically look for environment variables to initialize the SDK [2]. The standard approach is to store your keys in your project's .env or .env.local file using the key NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY [1][3]. Key points regarding the publishableKey: 1. Default Environment Loading: When you provide the key as an environment variable, the SDK automatically detects it. You can simplify your setup by omitting the publishableKey prop from the ClerkProvider component in your layout files [1][4]. 2. Middleware Configuration: Similarly, when using clerkMiddleware, the SDK resolves the necessary configuration (including the publishable key) from your environment variables [5]. While clerkMiddleware does support passing keys as arguments for advanced use cases (like dynamic multi-tenant setups), it is not required for standard configurations [5]. 3. Recommended Setup: The recommended way to ensure your environment is configured correctly is to use the Clerk CLI by running npx clerk@latest env pull [1][2]. This command automatically retrieves the correct keys from your Clerk Dashboard and saves them to your .env file with the required variable names [1][2]. If you are using TypeScript, modern versions of the Clerk SDK have been updated to treat the publishableKey prop as optional in ClerkProvider, reflecting that it will correctly fall back to environment variables at runtime [4].

Citations:


🌐 Web query:

Clerk Next.js configuration missing publishableKey CLERK_SECRET_KEY not set error

💡 Result:

The error "Missing publishableKey" or "CLERK_SECRET_KEY not set" typically occurs because the Clerk SDK cannot access your environment variables during the build process or at runtime [1][2][3]. ### Recommended Troubleshooting Steps 1. Verify Environment Variable Names Ensure your environment variables are named exactly as expected by Clerk [4][5]. * For the publishable key: Use NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY [4][5]. The NEXT_PUBLIC_ prefix is required for Next.js to expose this variable to the client-side [4]. * For the secret key: Use CLERK_SECRET_KEY [4][5]. This must never be exposed to the client [6]. 2. Check Deployment Platform Configuration If this error occurs on a hosting platform (e.g., Vercel, Netlify, AWS Amplify) but works locally, the platform may not be injecting the variables during the build phase [2][3]. * Ensure variables are set for all environments: Confirm they are added to your dashboard's production, preview, and development environment settings [3]. * Build-time injection: Some platforms require you to explicitly include environment variables in your build command if they are not picked up automatically [2]. For example, in some CI/CD pipelines, you may need to write the variables to a .env.production file during the build process [2]: bash env | grep -e NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY >> .env.production env | grep -e CLERK_SECRET_KEY >> .env.production npm run build 3. Use the Clerk CLI The most reliable way to set up these variables is using the Clerk CLI, which automatically pulls and configures them for your project [6][7]. * Run the following command to pull your keys directly into your .env.local file [7]: bash npx clerk@latest env pull * This command is idempotent and helps ensure the keys are correctly formatted and placed [7]. 4. Explicitly Pass Keys (If Needed) If standard environment variable loading still fails, you can explicitly pass the keys to the ClerkProvider in your layout file [1][3]. Note that this is generally a fallback and should not be necessary with a correct environment configuration [8]. jsx <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY} > {children} </ClerkProvider> ### Important Security Reminder Never commit your CLERK_SECRET_KEY to version control [6][7]. Always keep it in files ignored by git (e.g., .env.local) and ensure it is only available to server-side environments [4][6].

Citations:


Cụ thể hóa môi trường Clerk cho E2E.

Các kiểm thử này mong đợi trạng thái missing trên /sign-in, /sign-up, và 503 trên /app. Hiện cấu hình Playwright chỉ chạy pnpm dev, trong khi .env.example chứa các khóa placeholder hợp lý; nếu CI/giới thiệu người khác có .env.local thay thế với khóa hợp lệ, assertions sẽ fail. Trong cấu hình Playwright, bỏ qua CLERK_SECRET_KEY và truyền NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY placeholder vào server E2E, hoặc khởi động server với các biến này tường minh.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/example.spec.ts` around lines 15 - 57, Update the Playwright webServer
configuration used by “renders deterministic secretless sign-in and sign-up
states” and “fails closed...” so the E2E server explicitly runs with
CLERK_SECRET_KEY absent and NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY set to the
placeholder value. Prevent inherited .env.local credentials from affecting these
assertions while leaving the expected missing-configuration and 503 behavior
unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant