Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .agents/skills/tanstack-start-best-practices/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
name: tanstack-start-best-practices
description: TanStack Start best practices for full-stack React applications. Server functions, middleware, SSR, authentication, and deployment patterns. Activate when building full-stack apps with TanStack Start.
---

# TanStack Start Best Practices

Comprehensive guidelines for implementing TanStack Start patterns in full-stack React applications. These rules cover server functions, middleware, SSR, authentication, and deployment.

## When to Apply

- Creating server functions for data mutations
- Setting up middleware for auth/logging
- Configuring SSR and hydration
- Implementing authentication flows
- Handling errors across client/server boundary
- Organizing full-stack code
- Deploying to various platforms

## Rule Categories by Priority

| Priority | Category | Rules | Impact |
| -------- | ----------------- | ------- | --------------------------- |
| CRITICAL | Server Functions | 5 rules | Core data mutation patterns |
| CRITICAL | Security | 4 rules | Prevents vulnerabilities |
| HIGH | Middleware | 4 rules | Request/response handling |
| HIGH | Authentication | 4 rules | Secure user sessions |
| MEDIUM | API Routes | 1 rule | External endpoint patterns |
| MEDIUM | SSR | 6 rules | Server rendering patterns |
| MEDIUM | Error Handling | 3 rules | Graceful failure handling |
| MEDIUM | Environment | 1 rule | Configuration management |
| LOW | File Organization | 3 rules | Maintainable code structure |
| LOW | Deployment | 2 rules | Production readiness |

## Quick Reference

### Server Functions (Prefix: `sf-`)

- `sf-create-server-fn` — Use createServerFn for server-side logic
- `sf-input-validation` — Always validate server function inputs
- `sf-method-selection` — Choose appropriate HTTP method
- `sf-error-handling` — Handle errors in server functions
- `sf-response-headers` — Customize response headers when needed

### Security (Prefix: `sec-`)

- `sec-validate-inputs` — Validate all user inputs with schemas
- `sec-auth-middleware` — Protect routes with auth middleware
- `sec-sensitive-data` — Keep secrets server-side only
- `sec-csrf-protection` — Implement CSRF protection for mutations

### Middleware (Prefix: `mw-`)

- `mw-request-middleware` — Use request middleware for cross-cutting concerns
- `mw-function-middleware` — Use function middleware for server functions
- `mw-context-flow` — Properly pass context through middleware
- `mw-composability` — Compose middleware effectively

### Authentication (Prefix: `auth-`)

- `auth-session-management` — Implement secure session handling
- `auth-route-protection` — Protect routes with beforeLoad
- `auth-server-functions` — Verify auth in server functions
- `auth-cookie-security` — Configure secure cookie settings

### API Routes (Prefix: `api-`)

- `api-routes` — Create API routes for external consumers

### SSR (Prefix: `ssr-`)

- `ssr-data-loading` — Load data appropriately for SSR
- `ssr-hydration-safety` — Prevent hydration mismatches
- `ssr-streaming` — Implement streaming SSR for faster TTFB
- `ssr-selective` — Apply selective SSR when beneficial
- `ssr-prerender` — Configure static prerendering and ISR

### Environment (Prefix: `env-`)

- `env-functions` — Use environment functions for configuration

### Error Handling (Prefix: `err-`)

- `err-server-errors` — Handle server function errors
- `err-redirects` — Use redirects appropriately
- `err-not-found` — Handle not-found scenarios

### File Organization (Prefix: `file-`)

- `file-separation` — Separate server and client code
- `file-functions-file` — Use .functions.ts pattern
- `file-shared-validation` — Share validation schemas

### Deployment (Prefix: `deploy-`)

- `deploy-env-config` — Configure environment variables
- `deploy-adapters` — Choose appropriate deployment adapter

## How to Use

Each rule file in the `rules/` directory contains:

1. **Explanation** — Why this pattern matters
2. **Bad Example** — Anti-pattern to avoid
3. **Good Example** — Recommended implementation
4. **Context** — When to apply or skip this rule

## Full Reference

See individual rule files in `rules/` directory for detailed guidance and code examples.
236 changes: 236 additions & 0 deletions .agents/skills/tanstack-start-best-practices/rules/api-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# api-routes: Create Server Routes for External Consumers

## Priority: MEDIUM

## Explanation

While server functions are ideal for internal RPC, server routes provide traditional REST endpoints for external consumers, webhooks, and integrations. Use server routes when you need standard HTTP semantics, custom response formats, or third-party compatibility.

## Bad Example

```tsx
// Using server functions for webhook endpoints
export const stripeWebhook = createServerFn({ method: "POST" }).handler(async ({ request }) => {
// Server functions aren't designed for raw request handling
// No easy access to raw body for signature verification
// Response format is JSON by default
});

// Or exposing internal functions to external consumers
export const getUsers = createServerFn().handler(async () => {
return db.users.findMany();
});
// No versioning, no standard REST semantics
```

## Good Example: Basic Server Route

```tsx
// routes/api/users.ts
import { createFileRoute } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";

export const Route = createFileRoute("/api/users")({
server: {
handlers: {
GET: async ({ request }) => {
const users = await db.users.findMany({
select: { id: true, name: true, email: true },
});

return json(users, {
headers: {
"Cache-Control": "public, max-age=60",
},
});
},

POST: async ({ request }) => {
const body = await request.json();

// Validate input
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return json({ error: parsed.error.flatten() }, { status: 400 });
}

const user = await db.users.create({ data: parsed.data });
return json(user, { status: 201 });
},
},
},
});
```

## Good Example: Webhook Handler

```tsx
// routes/api/webhooks/stripe.ts
import { createFileRoute } from "@tanstack/react-router";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export const Route = createFileRoute("/api/webhooks/stripe")({
server: {
handlers: {
POST: async ({ request }) => {
const signature = request.headers.get("stripe-signature");
if (!signature) {
return new Response("Missing signature", { status: 400 });
}

// Get raw body for signature verification
const rawBody = await request.text();

let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
rawBody,
signature,
process.env.STRIPE_WEBHOOK_SECRET!,
);
} catch (err) {
console.error("Webhook signature verification failed:", err);
return new Response("Invalid signature", { status: 400 });
}

// Handle the event
switch (event.type) {
case "checkout.session.completed":
await handleCheckoutComplete(event.data.object);
break;
case "customer.subscription.updated":
await handleSubscriptionUpdate(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}

return new Response("OK", { status: 200 });
},
},
},
});
```

## Good Example: RESTful Resource with Dynamic Params

```tsx
// routes/api/posts/$postId.ts
import { createFileRoute } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";

export const Route = createFileRoute("/api/posts/$postId")({
server: {
handlers: {
GET: async ({ params }) => {
const post = await db.posts.findUnique({
where: { id: params.postId },
});

if (!post) {
return json({ error: "Post not found" }, { status: 404 });
}

return json(post);
},

PUT: async ({ request, params }) => {
const body = await request.json();
const parsed = updatePostSchema.safeParse(body);

if (!parsed.success) {
return json({ error: parsed.error.flatten() }, { status: 400 });
}

const post = await db.posts.update({
where: { id: params.postId },
data: parsed.data,
});

return json(post);
},

DELETE: async ({ params }) => {
await db.posts.delete({ where: { id: params.postId } });
return new Response(null, { status: 204 });
},
},
},
});
```

## Good Example: With Route-Level Middleware

```tsx
// routes/api/protected/data.ts
import { createFileRoute } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";
import { apiKeyMiddleware } from "@/lib/middleware";

export const Route = createFileRoute("/api/protected/data")({
server: {
// Middleware applies to all handlers in this route
middleware: [apiKeyMiddleware],
handlers: {
GET: async ({ request, context }) => {
// context.client available from middleware
const data = await fetchDataForClient(context.client.id);
return json(data);
},
},
},
});
```

## Good Example: Using createHandlers for Handler-Specific Middleware

```tsx
// routes/api/admin/users.ts
import { createFileRoute } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";

export const Route = createFileRoute("/api/admin/users")({
server: {
middleware: [authMiddleware], // All handlers require auth
handlers: (createHandlers) => ({
GET: createHandlers.GET(async ({ context }) => {
const users = await db.users.findMany();
return json(users);
}),

// DELETE requires additional admin middleware
DELETE: createHandlers.DELETE({
middleware: [adminOnlyMiddleware],
handler: async ({ request, context }) => {
const { userId } = await request.json();
await db.users.delete({ where: { id: userId } });
return json({ deleted: true });
},
}),
}),
},
});
```

## Server Functions vs Server Routes

| Feature | Server Functions | Server Routes |
| ------------------ | ---------------- | ------------------ |
| Primary use | Internal RPC | External consumers |
| Type safety | Full end-to-end | Manual |
| Response format | JSON (automatic) | Any (manual) |
| Raw request access | Limited | Full |
| URL structure | Auto-generated | Explicit paths |
| Webhooks | Not ideal | Designed for |

## Context

- Server routes use `createFileRoute` with a `server.handlers` property
- Support all HTTP methods: GET, POST, PUT, PATCH, DELETE, etc.
- Use `json()` helper for JSON responses
- Return `Response` objects for custom formats
- Handler receives `{ request, params }` object
- Ideal for: webhooks, public APIs, file downloads, third-party integrations
- Consider versioning: `/api/v1/users` for public APIs
Loading
Loading