204 lines
5.7 KiB
Markdown
204 lines
5.7 KiB
Markdown
# apps/web-next -- Next.js 15 Reference App
|
|
|
|
## 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`.
|
|
|
|
## Port: 3000
|
|
|
|
```bash
|
|
pnpm dev --filter @repo/web-next # http://localhost: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 |
|
|
|
|
## tRPC Endpoint Setup
|
|
|
|
The file `src/app/api/trpc/[trpc]/route.ts` creates a catch-all API route that handles all tRPC requests:
|
|
|
|
```typescript
|
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
import { appRouter } from "@repo/api";
|
|
|
|
const handler = (req: Request) =>
|
|
fetchRequestHandler({
|
|
endpoint: "/api/trpc",
|
|
req,
|
|
router: appRouter,
|
|
createContext: () => ({}),
|
|
});
|
|
|
|
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:
|
|
|
|
```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";
|
|
|
|
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>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
| 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 |
|
|
|
|
## 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`
|