docs(agents): write per-app AGENTS.md for cms, web-next, web-tanstack, storybook

This commit is contained in:
2026-05-05 10:00:28 +02:00
parent 0ede53998f
commit 2c6c836206
4 changed files with 232 additions and 381 deletions

View File

@@ -1,8 +1,10 @@
# apps/web-next -- Next.js 15 Reference App
# AGENTS.md — apps/web-next
Next.js 15 reference application using App Router. Demonstrates consuming feature packages via tRPC, using `@repo/core-trpc/next` for client setup, and importing UI components from `@repo/core-ui`.
## Purpose
Next.js 15 reference application using App Router. Demonstrates how to consume `@repo/api-client` for tRPC data fetching, `@repo/ui` for components, and `@repo/api` for the tRPC HTTP endpoint. This is a thin app -- business logic lives in `@repo/core`, UI components live in `@repo/ui`.
Thin app showcasing how features work end-to-end. Business logic lives in feature packages (`@repo/auth`, `@repo/blog`, etc.); UI primitives live in `@repo/core-ui`; this app is mostly routes, layouts, and component composition.
## Port: 3000
@@ -10,22 +12,32 @@ Next.js 15 reference application using App Router. Demonstrates how to consume `
pnpm dev --filter @repo/web-next # http://localhost:3000
```
Requires `@repo/cms` and PostgreSQL running to fetch live data:
```bash
docker compose up -d postgres # PostgreSQL on port 5432
pnpm dev --filter @repo/cms # Payload admin on port 3001
pnpm dev --filter @repo/web-next # Next.js on port 3000
```
## Key Files
| File | Purpose |
|---|---|
| `src/app/layout.tsx` | Root layout -- wraps children with `<Providers>`, sets HTML metadata |
| `src/app/providers.tsx` | Client component that wraps the app with `<ApiProvider trpcUrl="/api/trpc">` |
| `src/app/page.tsx` | Home page (server component by default) |
| `src/app/api/trpc/[trpc]/route.ts` | tRPC HTTP endpoint using the Next.js fetch adapter |
| `src/app/layout.tsx` | Root layout wraps app with `<TrpcProvider>` from `@repo/core-trpc/next` |
| `src/app/providers.tsx` | Client component for tRPC + React Query setup |
| `src/app/page.tsx` | Home page — navigation + marketing content |
| `src/app/blog/[slug]/page.tsx` | Dynamic blog post route |
| `src/app/api/trpc/[trpc]/route.ts` | tRPC fetch adapter endpoint |
| `e2e/` | Playwright end-to-end tests |
## tRPC Endpoint Setup
## tRPC Setup
The file `src/app/api/trpc/[trpc]/route.ts` creates a catch-all API route that handles all tRPC requests:
The tRPC endpoint handler (in `src/app/api/trpc/[trpc]/route.ts`):
```typescript
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "@repo/api";
import { appRouter } from "@repo/core-api";
const handler = (req: Request) =>
fetchRequestHandler({
@@ -38,149 +50,14 @@ const handler = (req: Request) =>
export { handler as GET, handler as POST };
```
How it works:
1. Next.js catch-all route `[trpc]` matches any path under `/api/trpc/`
2. `fetchRequestHandler` from tRPC's fetch adapter processes the request
3. `appRouter` from `@repo/api` contains all registered routers
4. `createContext` provides the context object to all procedures (currently empty `{}`)
5. Both GET (for queries) and POST (for mutations/batched queries) are exported
## Provider Setup
The `<ApiProvider>` from `@repo/api-client` must wrap the entire app. Since it uses React hooks, it lives in a `"use client"` component:
```tsx
// src/app/providers.tsx
"use client";
import { ApiProvider } from "@repo/api-client";
export function Providers({ children }: { children: React.ReactNode }) {
return <ApiProvider trpcUrl="/api/trpc">{children}</ApiProvider>;
}
```
```tsx
// src/app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
```
## Recipe: Adding a New Page with Data Fetching
This example adds an `/articles` page that lists published articles.
### Step 1: Create the page route
Create `src/app/articles/page.tsx`:
```tsx
import { ArticleList } from "./article-list";
export default function ArticlesPage() {
return (
<main>
<h1>Articles</h1>
<ArticleList />
</main>
);
}
```
### Step 2: Create the client component with data fetching
Create `src/app/articles/article-list.tsx`:
```tsx
"use client";
import { useTRPC } from "@repo/api-client";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@repo/ui";
export function ArticleList() {
const trpc = useTRPC();
const { data, isLoading, error } = useQuery(
trpc.content.listArticles.queryOptions({ status: "published", limit: 20 })
);
if (isLoading) return <p>Loading articles...</p>;
if (error) return <p>Error loading articles: {error.message}</p>;
return (
<ul>
{data?.map((article) => (
<li key={article.id}>
<h2>{article.title}</h2>
<Button variant="outline" size="sm">
Read more
</Button>
</li>
))}
</ul>
);
}
```
Key patterns:
- The page component (`page.tsx`) is a server component by default -- no `"use client"` needed
- Data-fetching components that use `useTRPC()` must be client components (`"use client"`)
- Import UI components from `@repo/ui`, never recreate them locally
## Payload Initialization Pattern (Server-Side Local API)
For server-side access to Payload CMS data (e.g., in server components, API routes, or server actions), create a Payload client initializer:
The provider (in `src/app/providers.tsx`):
```typescript
// src/lib/payload.ts
import { getPayload } from "payload";
import config from "@repo/cms-core/src/payload.config";
import { createPayloadClient, type PayloadClient } from "@repo/cms-client";
"use client";
import { TrpcProvider } from "@repo/core-trpc/next";
let cachedClient: PayloadClient | null = null;
export async function getPayloadClient(): Promise<PayloadClient> {
if (cachedClient) return cachedClient;
const payload = await getPayload({ config });
cachedClient = createPayloadClient({ mode: "local", payload });
return cachedClient;
}
```
Usage in a server component:
```tsx
// src/app/articles/page.tsx (server component)
import { getPayloadClient } from "@/lib/payload";
export default async function ArticlesPage() {
const client = await getPayloadClient();
const result = await client.find("articles", {
where: { status: { equals: "published" } },
sort: "-publishedAt",
limit: 20,
});
return (
<main>
<h1>Articles</h1>
<ul>
{result.docs.map((article) => (
<li key={article.id}>{article.title}</li>
))}
</ul>
</main>
);
export function Providers({ children }: React.ReactNode) {
return <TrpcProvider>{children}</TrpcProvider>;
}
```
@@ -188,16 +65,43 @@ export default async function ArticlesPage() {
| Dependency | Purpose |
|---|---|
| `@repo/api` | `appRouter` for the tRPC HTTP endpoint |
| `@repo/api-client` | `ApiProvider` + `useTRPC()` for client-side data fetching |
| `@repo/ui` | Shared UI components (Button, Input, Label, FormField, etc.) |
| `next` | Next.js 15 framework with App Router |
| `react` / `react-dom` | React 19 runtime |
| `@repo/core-api` | `appRouter` for tRPC endpoint |
| `@repo/core-trpc/next` | Next.js tRPC client + provider |
| `@repo/core-ui` | Design system components |
| `@repo/auth`, `@repo/blog`, etc. | Feature packages (indirectly via core-api) |
| `next` | Next.js 15 framework |
| `@trpc/server` | tRPC server (fetch adapter) |
## Test conventions
- Unit tests colocated: `src/app/blog/article-list.test.tsx`
- Vitest environment: `jsdom`
- e2e tests in `e2e/` folder: `*.spec.ts`
- Run: `pnpm test --filter @repo/web-next` (units) or `pnpm test:e2e` (Playwright)
## E2E Test Setup
Playwright config in `e2e/playwright.config.ts`:
```typescript
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
webServer: {
command: "pnpm dev",
port: 3000,
reuseExistingServer: !process.env.CI,
},
use: { ...devices["Desktop Chrome"].use },
});
```
Run: `pnpm test:e2e` starts the dev server and runs all `.spec.ts` files.
## Cross-References
- **tRPC routers:** `packages/api/` -- see `packages/api/AGENTS.md`
- **tRPC client/hooks:** `packages/api-client/` -- see `packages/api-client/AGENTS.md`
- **UI components:** `packages/ui/` -- see `packages/ui/AGENTS.md`
- **CMS client:** `packages/cms-client/` -- see `packages/cms-client/AGENTS.md`
- **CMS config:** `packages/cms-core/` -- see `packages/cms-core/AGENTS.md`
- **Feature packages:** `packages/{auth,blog,media,marketing-pages,navigation}/`
- **tRPC composition:** `packages/core-api/AGENTS.md`
- **tRPC client + provider:** `packages/core-trpc/AGENTS.md`
- **UI components:** `packages/core-ui/AGENTS.md`