利用AI Agent开发AI 新闻分析生产级项目(三)
·
prompts\news-details-page-ui.md
# Prompt: News Details Page UI (SKEW)
## Goal
Build the SKEW **news details page** at `app/news/[id]/page.tsx` to match the attached
reference: the shared top bar + site header, a two-column article layout (main article
column + analysis sidebar), a "Related Stories" grid, a newsletter CTA band, and the
shared footer. Display-only. No scraping, no analysis, no pipeline mutation, no live
Supabase reads yet (data layer not built) — render from typed mock data so the layout can
be validated pixel-close now and swapped to real Supabase queries later without touching
the presentational components. UI must display stored data only (AGENTS.md §5).
## Skills read
- `AGENTS.md` — §1 (build list: news details page with full article analysis), §5
(architecture layers — UI displays data only, no scrape/analyze/mutate), §19 (news
details page must show summary, sentiment, framing percentages, confidence, framing
notes, loaded terms, disclaimer; AI-estimated framing labelling; framing label is one
of left/center/right/mixed/unclear), §20 (Related Articles section, up to 5), §21
(standards, server/client boundaries), §22 (checks).
- No feature skill applies (clerk/supabase/oxylabs/ai-sdk out of scope for pure UI). Per
§3, Tailwind/shadcn-style work uses existing project patterns + `@theme` tokens.
- `prompts/design-system.md` and `prompts/home-page-ui.md` — the established token +
primitive + layout foundation this builds on.
## Existing code inspected
- `app/globals.css` — Tailwind v4 `@theme` tokens: colors (`text-primary`,
`text-secondary`, `surface`, `bg-primary`, `bg-secondary`, `border`, `divider`,
`bias-left`/`center`/`right`, `accent`), radius, shadow, `--container-app: 1280px`, and
`.text-h1…caption` type utilities.
- `app/page.tsx` — home page composing `TopBar`, `SiteHeader`, `CategoryBar`,
`SiteFooter`, and a `NewsCard` grid from `MOCK_ARTICLES`.
- `components/layout/` — `top-bar.tsx`, `site-header.tsx`, `category-bar.tsx`,
`site-footer.tsx` (all presentational, reusable as-is).
- `components/ui/` — `bias-meter.tsx` (`{left, center, right}`, optional `showScale`,
optional `compact`; segmented full-width bar), `news-card.tsx`, `article-card.tsx`
(horizontal card with image/excerpt/bias/time), `button.tsx`
(primary/secondary/outline/text, sm/md), `chip.tsx`, `badge.tsx` (small meta label).
- `lib/types.ts` — `BiasBreakdown`, `NewsArticleCard`. `lib/mock/news.ts` —
`MOCK_ARTICLES`. `lib/utils.ts` — `cn()`.
## Decisions / assumptions
- **Route.** Add `app/news/[id]/page.tsx`. `params` is async in Next 16 — `await params`
before reading `id`. Look the article up by id from mock data; call `notFound()` when
missing. (Confirm the async-params + `notFound` conventions against
`node_modules/next/dist/docs/` before coding, since Next APIs here differ from training.)
- **Make detail reachable.** Wrap `NewsCard` content in a `next/link` to `/news/{id}` so
home cards navigate to the detail page. Additive, presentational — no other card change.
Related-story cards on the detail page also link to their `/news/{id}`.
- **Mock data, typed.** Add a `NewsArticleDetail` type to `lib/types.ts` mirroring the
§19/§20 fields the eventual Supabase query returns, and a `MOCK_ARTICLE_DETAILS` map +
`getArticleDetail(id)` helper to `lib/mock/news.ts`. When real queries land, only the
page's data source changes — components stay presentational. Fields:
- identity/meta: `id`, `title`, `category`, `country`, `author`, `publishedDate` (display
string), `readTime`, `imageUrl`, `imageCaption`, `imageCredit`
- body: `bodyParagraphs: string[]`
- framing: `bias: BiasBreakdown` (left/center/right, sum 100), `biasLabel`
(`left`|`center`|`right`|`mixed`|`unclear`), `sentimentLabel`
(`positive`|`neutral`|`negative`), `confidence` (0–1, optional), `framingNotes`,
`loadedTerms: string[]`, `disclaimer`
- summary: `summaryPoints: string[]`, `summaryGenerated` (display string), `summaryReadTime`
- sources: `sourceCount`, `topSources: { name: string; bias: 'left'|'center'|'right' }[]`
- related: `related: NewsArticleCard[]` (up to 5 per §20; reference shows 6 — cap the
rendered list at 5 to honour §20, note the cap in a comment)
- **AI-estimated framing.** Label the framing/bias sections as **AI-estimated**, not
objective truth (§19). The bias percentages drive both the inline "Bias Distribution"
bar and the sidebar "Bias Analysis" card; the "Overall Bias" headline reads the strongest
segment (e.g. `Right 49%`), matching the label unless confidence is low (mock data is
internally consistent).
- **Server Components by default.** Page and all detail components are static Server
Components. No client interactivity is required (Save/Share/feedback/newsletter are
visual only — Clerk/forms not wired). Add `"use client"` to nothing here.
- **Reuse over fork.** Reuse `BiasMeter` for both the inline distribution bar and the
sidebar per-row bars where it fits; reuse `Button` for all CTAs; reuse layout
`TopBar`/`SiteHeader`/`SiteFooter`. Do not modify existing primitives except the additive
`NewsCard` link wrap.
## Files likely to change / add
- `app/news/[id]/page.tsx` — detail route: compose top bar, header, article column +
sidebar, related stories, newsletter CTA, footer from mock detail data.
- `components/article/article-header.tsx` — breadcrumb (`Category · Country`), `text-h1`
title, byline row (`By {author} · {date} · {readTime}` left; Save / Share / more actions
right as visual icon buttons).
- `components/article/article-hero.tsx` — 16:9 hero image, caption + `Photo: {credit}`.
- `components/article/bias-distribution.tsx` — bordered inline "Bias Distribution" panel
(info icon, full-width `BiasMeter` with L/Center/Right, `{n} sources` under it).
- `components/article/article-body.tsx` — renders `bodyParagraphs` as `text-body-lg`
paragraphs with comfortable measure and spacing.
- `components/article/related-stories.tsx` + `components/ui/related-story-card.tsx` —
"Related Stories" heading + 2-col grid of compact cards (small thumbnail, `Category ·
Country` caption, title, `date · readTime`). Capped at 5 (§20).
- `components/article/sidebar/bias-analysis-card.tsx` — "Bias Analysis" card: title + info
icon, "Overall Bias" `{Label} {pct}%` (accent), "Based on {n} balanced sources", three
Left/Center/Right rows (label, %, thin colored bar), explanatory copy, "How We Analyze
Bias" outline button. Framed as AI-estimated.
- `components/article/sidebar/ai-summary-card.tsx` — "AI Summary" card: title + info icon,
"Generated {date} · {readTime}", bulleted `summaryPoints`, "AI summaries can make
mistakes." disclaimer, "Provide Feedback" outline button.
- `components/article/sidebar/source-breakdown-card.tsx` — "Source Breakdown" card: title +
info icon, "{n} Total Sources", Left/Center/Right count rows with bars, a `Top Sources /
Bias` two-column list (source name + colored bias label), "View All Sources" button.
- `components/ui/sidebar-card.tsx` — shared bordered white card shell with header
(title + info icon) used by the three sidebar cards, to avoid repetition.
- `components/layout/newsletter-cta.tsx` — "Stay Informed. Stay Balanced." band: heading +
subcopy left, email input + Subscribe button right (visual only, no submit).
- `lib/types.ts` — add `NewsArticleDetail` (and small helper unions for labels).
- `lib/mock/news.ts` — add `MOCK_ARTICLE_DETAILS` + `getArticleDetail(id)`; seed the "Trump
Sends Iran…" story from the reference plus a couple more so navigation from other cards
degrades gracefully (fallback detail generated from the card, or `notFound()`).
- `components/ui/news-card.tsx` — wrap in `Link` to `/news/{id}` (additive).
## Visual interpretation (from reference)
- **Overall layout.** Below the shared header, a centered `max-w-(--container-app)` region
with 24px side padding, split into a two-column grid: main article column (~2fr) + sticky
analysis sidebar (~1fr, ~360px), 32–40px gutter. Collapses to a single column below
`lg`; sidebar stacks under the article on mobile.
- **Article header.** Small secondary breadcrumb `Politics · United States`; large bold
`text-h1` title (2 lines); byline row `By David Morgan · May 31, 2026 · 12 min read` in
secondary text on the left, and Save (bookmark) / Share / ⋯ ghost icon-buttons on the
right.
- **Hero.** Full-width 16:9 image, `rounded-md`; below it a `text-caption` secondary caption
and a `Photo: Andrew Harnik/Getty Images` credit.
- **Bias Distribution panel.** A bordered `rounded-lg` white box: "Bias Distribution" label
+ info icon; a full-width segmented L/Center/Right bar (`BiasMeter`, tokens
`bias-left`/`center`/`right`, labels `Left 20%` / `Center 31%` / `Right 49%`); `12
sources` below.
- **Body.** Comfortable `text-body-lg` paragraphs, ~16px, generous line-height and
paragraph spacing; a pull quote paragraph reads inline (no special styling required).
- **Related Stories.** Section heading, then a 2-col grid of compact horizontal cards:
~64–80px square thumbnail left, `Category · Country` caption, 2-line title, `date ·
readTime` meta. Cap at 5.
- **Sidebar — Bias Analysis.** White `rounded-lg` bordered card. Header "Bias Analysis" +
info icon. "Overall Bias" small label; big accent `Right 49%`; "Based on 12 balanced
sources" (accent link-style). Three rows: `Left 20%` with a thin red bar,
`Center 31%` grey, `Right 49%` blue — bar width ∝ percentage. Then two lines of
explanatory copy in secondary text. "How We Analyze Bias" full-width outline button.
- **Sidebar — AI Summary.** "AI Summary" + info icon; "Generated May 31, 2026 · 3 min read"
caption; 5 bulleted key points (`text-body-md`); "AI summaries can make mistakes."
secondary caption; "Provide Feedback" outline button.
- **Sidebar — Source Breakdown.** "Source Breakdown" + info icon; "12 Total Sources"; three
Left/Center/Right count rows (`2 (20%)` etc.) with bars; a `Top Sources / Bias` table
where each row is a source name + a right-aligned bias label colored by lean (Right =
blue, Left = red, Center = secondary); "View All Sources" button.
- **Newsletter CTA.** Light `surface` band, `rounded-lg`: bold "Stay Informed. Stay
Balanced." + subcopy left; email `<input>` + dark Subscribe button right. Visual only.
- **Colors/spacing/type.** Use existing `@theme` tokens and `.text-*` utilities only — no
new hardcoded hex. Bias colors always from `bias-left`/`bias-center`/`bias-right`.
## Implementation requirements
- TypeScript, explicit prop types, no `any`. Small focused components; no mixed UI/business
logic. Keep the page handler thin — compose components, no data-shaping beyond the mock
lookup.
- Preserve server/client boundaries (§21): all presentational Server Components; no
`"use client"`, no network calls, no secrets, no pipeline state.
- Use design tokens + type utilities, not ad-hoc hex/px where a token exists. Reuse
`BiasMeter`, `Button`, and the layout components — extend, don't fork.
- Inline SVGs for icons (bookmark, share, ⋯, info, chevron), matching existing
`article-card`/`top-bar` icon style; no new icon dependency.
- Framing/bias UI must read as **AI-estimated** (§19), not objective fact. Percentages sum
to 100; framing label matches the strongest segment in the mock data.
- Next 16: `params` is a Promise — `await params`; use `notFound()` for unknown ids. Verify
against `node_modules/next/dist/docs/` before writing.
- No scraping/analysis/network/secret code anywhere in these components (§21).
## Security requirements
- Pure UI; no secrets, no server-only keys, no network calls, no pipeline state, no browser
execution of Oxylabs/OpenAI/scraping/analysis. Nothing to expose.
## Acceptance criteria
- `/news/1` renders top bar, header, article header/byline, hero + caption, inline Bias
Distribution bar, article body, Related Stories grid, the three sidebar analysis cards
(Bias Analysis, AI Summary, Source Breakdown), newsletter CTA, and footer — visually
matching the reference.
- Sidebar Bias Analysis shows Overall Bias `{Label} {pct}%`, per-lean rows with
proportional colored bars, and framing presented as AI-estimated.
- AI Summary lists the bulleted key points with the "AI summaries can make mistakes."
disclaimer. Source Breakdown lists per-lean counts and the Top Sources / Bias list.
- Bias percentages sum to 100 everywhere they appear; bias colors come only from the bias
tokens.
- Two-column desktop layout collapses to one column below `lg` with the sidebar stacked;
no horizontal page overflow at 375px. Related Stories is 2-col on desktop, 1-col mobile,
capped at 5.
- Home `NewsCard`s and Related-story cards link to `/news/{id}`; unknown ids `notFound()`.
- Existing pages (`/`, `/design-system`) and `article-card.tsx` remain unchanged and still
build. Light-only, deterministic, Poppins throughout.
## Checks to run
- `npm run typecheck`
- `npm run lint`
- `npm run build` (new route + components)
## Manual test steps
1. `npm run dev`.
2. Open `http://localhost:3000/` and click the first "Trump Sends Iran…" card — confirm it
navigates to `/news/1`.
3. On `/news/1`, verify against the reference: breadcrumb, title, byline + action icons,
hero image with caption/credit, inline Bias Distribution bar (`Left 20% / Center 31% /
Right 49%`, `12 sources`), article body, Related Stories grid, and the three sidebar
cards (Bias Analysis with `Right 49%`, AI Summary bullets + disclaimer, Source Breakdown
with Top Sources list), newsletter band, and footer.
4. Resize to ~1024px and ~768px — confirm the sidebar stacks under the article and Related
Stories collapses to one column; at ~375px confirm no horizontal overflow.
5. Visit `/news/999` (unknown id) — confirm the Next not-found page renders.
6. Confirm `/` and `/design-system` still render unchanged.
这份 prompts/news-details-page-ui.md 文件是 AI 助手生成的第三份详细实施计划书。
在完成了“设计系统”和“首页 UI”后,这份计划书旨在搭建 SKEW 网站的新闻详情页。这是整个应用最核心的页面之一,因为它要展示 AI 分析新闻后的所有结果(情感、偏见、摘要、来源等)。
下面为你逐个模块进行技术拆解:
1. Goal (目标)
解释:
- 核心任务:搭建详情页(
app/news/[id]/page.tsx)。页面结构包含:共享的顶部栏/Header、双栏布局(左侧文章正文 + 右侧 sticky 分析侧边栏)、相关阅读网格、邮件订阅条、Footer。 - 关键约束:依然是纯 UI 展示。必须造强类型的 Mock 数据来渲染页面,绝对不能在此阶段接入真实的爬虫或 AI 分析接口。UI 组件只负责展示数据,不负责产生数据。
2. Decisions / assumptions (关键决策与技术选型)
解释:
AI 在面对具体技术细节时做出的决策非常精准:
- Next.js 16 异步参数:AI 注意到项目使用的是魔改/最新的 Next.js 16。在 Next 15+ 中,
params变成了一个 Promise,必须写await params才能拿到 URL 里的id。AI 强调要先去查本地node_modules里的文档确认这个语法。 - 打通路由跳转:为了让详情页能被访问到,AI 决定微调首页的
NewsCard组件,用<Link>把它包裹起来,指向/news/{id}。 - 极度详细的 Mock 数据结构:为了满足
AGENTS.md第 19/20 条的要求,AI 设计了一个庞大的NewsArticleDetail类型,包含了:正文段落、偏见百分比、偏见标签、情感标签、置信度、AI 摘要要点、来源列表等。这确保了未来接真数据库时,数据结构完全对标。 - 强调“AI 估算”免责声明:在 UI 展示上,必须明确标注偏见分析是 AI-estimated(AI 估算的),而不是客观事实,防止误导读者。
- 零客户端交互:整个详情页(包括侧边栏的吸顶效果、收藏分享按钮)全部是 Server Component。分享、收藏只是画个图标,不加
"use client"。
3. Files likely to change / add (即将修改/新增的文件清单)
解释:
AI 列出了非常清晰的组件化施工蓝图,将详情页拆得非常细:
- 文章主体区:面包屑标题 (
article-header)、头图 (article-hero)、内联偏见分布条 (bias-distribution)、正文 (article-body)。 - 侧边栏分析区:抽离出一个通用外壳
sidebar-card,然后基于它建三个核心分析卡:bias-analysis-card:总体偏见、各阵营比例条。ai-summary-card:AI 提取的要点总结 + “AI 会犯错”免责声明。source-breakdown-card:新闻来源数量及它们的偏见倾向。
- 其他:相关阅读卡片 (
related-story-card)、邮件订阅条 (newsletter-cta)。
4. Visual interpretation (视觉与布局还原要求)
解释:
这是给 AI 的“像素级”还原指南:
- 双栏布局:桌面端是左大右小(约 2:1),右侧侧边栏要
sticky(滚动时吸顶)。在lg(大屏幕)以下变成单栏,侧边栏堆叠到文章下方。 - 内联偏见条:文章开头放一个带边框的白盒子,展示
Left 20% / Center 31% / Right 49%,加起来必须是 100%。 - 来源明细表:右侧侧边栏要列出“顶级来源”,比如“纽约时报 - 偏左(红标签)”、“福克斯新闻 - 偏右(蓝标签)”,颜色必须严格使用设计系统里的令牌。
- 无横向溢出红线:在手机端(375px)测试时,绝对不能出现横向滚动条。
5. Implementation requirements (实现要求)
解释:
- Next 16 适配:重申必须用
await params和notFound()(当访问不存在的文章 ID 时,抛出 404 页面)。 - 零硬编码:所有颜色、圆角、字体必须用上一阶段建好的
@theme令牌。禁止在代码里写死#FF0000。 - 复用组件:必须复用已有的
BiasMeter、Button和首页的TopBar/SiteHeader等布局组件,只做增量扩展,不破坏老代码。
6. Security & Acceptance criteria (安全与验收标准)
解释:
- 安全:纯 UI,无任何网络请求或密钥操作。
- 验收标准:
- 访问
/news/1能看到完整的双栏布局和三个侧边栏分析卡。 - 偏见百分比加起来必须等于 100,颜色对应正确。
- 必须有“AI 会犯错”的免责声明。
- 首页点击卡片能跳转到详情页,输入错误的 ID(如
/news/999)能显示 404。 - 之前做好的首页和设计系统页必须保持原样能正常打开。
- 访问
7. Manual test steps (检查与测试步骤)
解释:
除了跑typecheck和build,AI 给出了人类测试的具体步骤:启动项目 -> 在首页点文章进详情页 -> 对比设计稿 -> 缩小窗口看响应式 -> 输入错误 ID 看 404 -> 确认首页没被改坏。
总结
这份计划书展示了非常成熟的前端工程化思维。AI 把一个复杂的新闻详情页拆解成了:路由层 (page.tsx)、数据层 (MOCK_ARTICLE_DETAILS)、组件层 (article-*, sidebar/*) 和基础零件层 (BiasMeter, Button)。
特别值得注意的是,AI 严格遵守了 AGENTS.md 的约束,在页面上醒目地标注了 “AI-estimated”(AI 估算) 和免责声明,这不仅是一个 UI 细节,更是新闻分析类产品的伦理与合规底线。只要你批准这份文档,AI 就会按图索骥,高效且安全地写出整个详情页的代码。
prompts\clerk-authentication.md
# Clerk Authentication
## Goal
Add Clerk authentication to SKEW: sign-in / sign-up flows, session-aware header UI, and route protection so that **news detail pages (`/news/[id]`) require a signed-in user** while the home feed stays public. Wire up all Clerk environment variables and keep the service/server boundaries intact.
## Skills read
- `.agents/skills/clerk/SKILL.md` (router) → version detection, routed to setup + Next.js patterns
- `.agents/skills/clerk-setup/SKILL.md` → ClerkProvider placement, middleware convention, env keys, current-SDK notes
- Live Clerk Next.js quickstart (`https://clerk.com/docs/nextjs/getting-started/quickstart`) → `clerkMiddleware`, `createRouteMatcher`, control components
- `node_modules/next/dist/docs/01-app/01-getting-started/16-proxy.md` → **Next.js 16 renames Middleware to Proxy; the file is `proxy.ts` at project root**
## Existing code inspected
- `package.json` → Next.js **16.2.10**, React 19, Tailwind v4. No Clerk installed. Node v24 (meets ≥20.9).
- `app/layout.tsx` → root layout; Poppins font on `<html>`, `<body className="min-h-full flex flex-col">`. **No provider yet.**
- `app/page.tsx` → public home feed (`/`).
- `app/news/[id]/page.tsx` → news detail page (to be protected).
- `components/layout/site-header.tsx` → has placeholder **"Subscribe"** and **"Login"** `<Button>`s in the top-right action cluster. This is where auth UI goes.
- `components/ui/button.tsx` → design-system button (`variant`, `size`). Reuse for triggers.
- No `middleware.ts` / `proxy.ts`, no `.env.example`, no `.env.local`.
## Decisions / assumptions
- **SDK**: current Clerk SDK → install `@clerk/nextjs` (latest v7+). Import client components from `@clerk/nextjs`, server helpers from `@clerk/nextjs/server`.
- **Middleware file**: Next.js 16 uses **`proxy.ts`** at project root (not `middleware.ts`). Export `clerkMiddleware()` as default; the matcher still uses the Clerk-recommended pattern. (Per Next 16 proxy doc + clerk-setup "Next.js ≤15: middleware.ts" note.)
- **Provider placement**: `<ClerkProvider>` goes **inside `<body>`** (current SDK requirement), wrapping the existing flex column so layout is unchanged.
- **Route protection** (user-confirmed): `/news/[id]` requires auth; everything else public. Use `createRouteMatcher(['/news/(.*)'])`; signed-out users hitting a protected route are redirected to sign-in via `auth.protect()`.
- **Auth pages**: dedicated catch-all routes `app/sign-in/[[...sign-in]]/page.tsx` and `app/sign-up/[[...sign-up]]/page.tsx` rendering `<SignIn />` / `<SignUp />`, matching the `NEXT_PUBLIC_CLERK_SIGN_IN_URL` / `_SIGN_UP_URL` env vars listed in AGENTS.md §21.
- **Header UI**: replace the placeholder "Login" button with Clerk control components. `<SignedOut>` → `<SignInButton>` (secondary Button) + keep/repurpose "Subscribe" as `<SignUpButton>`; `<SignedIn>` → `<UserButton />`. Keep design-system Buttons as the trigger children via `asChild`-style composition (`<SignInButton><Button variant="secondary" size="sm">Login</Button></SignInButton>`).
- No shadcn theme step: no `components.json` in the project.
- Keys: provided by the user in `.env.local` (or Clerk Keyless on first run). We only create `.env.example` with placeholders — never commit real keys.
## Files likely to change / add
- `package.json` / `package-lock.json` — add `@clerk/nextjs`
- `proxy.ts` — **new**, Clerk middleware + protected-route matcher
- `app/layout.tsx` — wrap children in `<ClerkProvider>`
- `components/layout/site-header.tsx` — Clerk control components in the action cluster
- `app/sign-in/[[...sign-in]]/page.tsx` — **new**, `<SignIn />`
- `app/sign-up/[[...sign-up]]/page.tsx` — **new**, `<SignUp />`
- `.env.example` — **new**, all Clerk vars from AGENTS.md §21 as placeholders
- `AGENTS.md` env table — already lists Clerk vars; no change needed unless a var differs
## Implementation requirements
1. `npm install @clerk/nextjs`.
2. `proxy.ts` at project root:
```ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isProtectedRoute = createRouteMatcher(['/news/(.*)'])
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) await auth.protect()
})
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|cur|heic|heif|mp4|mkv|mov|avi|ogv|flv|wmv|pdf|txt)).*)',
'/(api|trpc)(.*)',
],
}
app/layout.tsx: importClerkProviderfrom@clerk/nextjs, wrap the<body>children (provider inside<body>). Preserve existing classes and font variable.- Auth pages render
<SignIn />/<SignUp />centered on the page; keep it minimal and responsive using existing Tailwind tokens (e.g. a centered flex container onbg-surface). site-header.tsx: importSignedIn,SignedOut,SignInButton,SignUpButton,UserButtonfrom@clerk/nextjs. In theml-autoaction cluster:<SignedOut>:<SignUpButton>wrapping a primary Button (“Subscribe”),<SignInButton>wrapping a secondary Button (“Login”).<SignedIn>:<UserButton />.- This is a server component today; Clerk control components work in it. No
"use client"needed.
.env.exampleincludes (placeholders only):NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= CLERK_SECRET_KEY= NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/ NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
Security requirements
CLERK_SECRET_KEYis server-only; never referenced in client code or committed.- Only
NEXT_PUBLIC_*Clerk vars reach the browser (publishable key + URL config). .env.examplecontains placeholders only, no real credentials.- Protection enforced in
proxy.ts(server), not client-side conditional rendering alone.
Acceptance criteria
- App builds and type-checks with Clerk installed.
- Signed-out user: home page loads; header shows Login + Subscribe; visiting
/news/<id>redirects to Clerk sign-in. - After signing in: header shows
<UserButton />;/news/<id>loads normally. /sign-inand/sign-uprender Clerk’s prebuilt forms.- No secret key or non-public var exposed to the client bundle.
Checks to run
npm run typechecknpm run lintnpm run build(routes + middleware added → build is affected)
Manual test steps
- Add real Clerk keys to
.env.local(copy from.env.example; use devpk_test_/sk_test_from the Clerk Dashboard) — or rely on Keyless on firstnpm run dev. npm run dev.- Visit
http://localhost:3000/→ loads, header shows Login and Subscribe. - Click a news card / visit
http://localhost:3000/news/<any-id>while signed out → redirected to the Clerk sign-in page. - Sign up / sign in → redirected back; header now shows the UserButton avatar.
- Revisit
/news/<id>→ the detail page loads. - Open the UserButton → Sign out → confirm
/news/<id>redirects to sign-in again.
这份 `prompts/clerk-authentication.md` 文件是 AI 助手生成的第四份**详细实施计划书**。
在完成了纯 UI(设计系统、首页、详情页)的搭建后,这份计划书标志着项目正式进入**功能接入阶段**。它的核心任务是为 SKEW 网站接入 Clerk 用户认证系统,实现“首页公开,详情页需登录”的权限控制。
下面为你逐个模块进行技术拆解:
---
### 1. Goal (目标)
> **解释:**
* **核心任务**:接入 Clerk 认证。包含登录/注册页面、根据登录状态变化的 Header UI(显示登录按钮或用户头像)、以及**路由保护**:未登录用户可以看首页,但访问新闻详情页 `/news/[id]` 时会被拦截并跳转到登录页。
### 2. Skills read & Existing code inspected (查阅文档与代码摸底)
> **解释:**
这里发生了一个非常精彩的技术细节处理:
* **查阅文档**:AI 阅读了 Clerk 的技能文档和官方快速上手指南。最关键的是,它读了 Next.js 16 的本地文档,**发现 Next.js 16 把中间件改名成了 Proxy**,文件名从 `middleware.ts` 变成了 `proxy.ts`。这正是前面 `AGENTS.md` 反复强调“不要信记忆,去查本地文档”的原因!
* **摸底代码**:AI 发现项目还没装 Clerk。同时注意到之前做首页 UI 时,Header 右上角预留了 "Subscribe" 和 "Login" 两个占位按钮,现在正好可以把它们替换成真实的 Clerk 组件。
### 3. Decisions / assumptions (关键决策与技术选型)
> **解释:**
* **SDK 版本**:安装最新的 `@clerk/nextjs` (v7+)。
* **路由保护策略**:使用 `createRouteMatcher` 匹配 `/news/(.*)`。如果未登录用户硬闯详情页,调用 `auth.protect()` 自动重定向到登录页。
* **认证页面路由**:使用 Next.js 的 catch-all 路由 `app/sign-in/[[...sign-in]]/page.tsx`。双括号 `[[...]]` 表示可选捕获,这样 Clerk 的组件可以接管 `/sign-in` 甚至 `/sign-in/factor-one` 等多步验证路径,而不需要你手写每一层路由。
* **UI 组合复用**:为了不破坏之前做好的设计系统,AI 不会直接用 Clerk 默认丑陋的按钮,而是采用组合模式:用 Clerk 的 `<SignInButton>` 包裹住设计系统里的 `<Button>`(例如 `<SignInButton><Button variant="secondary">Login</Button></SignInButton>`),做到样式绝对可控。
* **Provider 放置位置**:根据最新版 Clerk SDK 要求,`<ClerkProvider>` 必须放在 `<body>` 标签内部,包裹住页面内容。
### 4. Implementation requirements (实现要求与核心代码)
> **解释:**
这里给出了具体的施工代码:
* **`proxy.ts` (拦截器)**:
给出了完整的中间件代码。`matcher` 配置了一大串正则表达式,目的是**排除所有静态文件**(图片、CSS、JS 等),确保 Clerk 拦截器只处理动态页面路由,不影响网站加载静态资源的速度。
* **保持服务端组件**:AI 特别强调,更新 `site-header.tsx` 时**不需要加 `"use client"`**。因为 Clerk 的控制组件(`<SignedIn>`, `<UserButton>` 等)内部已经处理好了服务端与客户端的边界,可以直接在 Server Component 中使用,这保证了页面的极简与高性能。
* **环境变量安全**:创建 `.env.example` 作为模板,里面只写变量名不填真实密钥。真实密钥只能放在 `.env.local` 里(被 Git 忽略)。
### 5. Security requirements (安全要求)
> **解释:**
认证模块的安全是重中之重:
* `CLERK_SECRET_KEY`(服务端密钥)绝对不能出现在前端代码中,前端只能用 `NEXT_PUBLIC_` 开头的公钥。
* **防线在服务端**:权限拦截必须在 `proxy.ts`(服务端代理层)执行,绝不能只靠前端写个 `if (user)` 来隐藏页面按钮。因为前端代码可以被篡改,但服务端拦截是无法绕过的。
### 6. Acceptance criteria & Manual test steps (验收与测试)
> **解释:**
* **验收标准**:未登录能看首页,看详情页会被踢到登录页;登录后 Header 变成头像,详情页正常显示;构建不报错。
* **手动测试步骤**:AI 给出了非常严谨的测试流:启动项目 -> 看首页按钮 -> 点详情页被拦截 -> 注册/登录 -> 跳回详情页 -> 点头像登出 -> 再次访问详情页确认又被拦截。形成了一个完整的闭环测试。
---
### 总结
这份计划书展示了 AI 如何安全、平稳地将第三方认证服务接入到现有的纯 UI 项目中。它不仅严格遵守了 `AGENTS.md` 中“先查文档防幻觉”的指令(发现了 Next 16 的 Proxy 命名变更),还完美遵守了“UI 只负责展示,不处理逻辑”的架构分层原则——路由保护交给了 `proxy.ts`,Header 只是被动响应登录状态。只要你批准这份计划,AI 就会严格按照上面的代码结构开始安装依赖并编写认证逻辑。
更多推荐

所有评论(0)