docs: rewrite AGENTS.md with full architecture and conventions
Add comprehensive agent instructions covering architecture, directory structure, auth patterns, i18n patterns, route constants, environment variables, and anti-patterns. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
65e91fc6f6
commit
8ff2246ecf
1 changed files with 264 additions and 0 deletions
264
AGENTS.md
264
AGENTS.md
|
|
@ -3,3 +3,267 @@
|
|||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
|
||||
# Agent Instructions
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a **Next.js 16 + Convex (self-hosted) + Better Auth** SaaS template.
|
||||
|
||||
- **Frontend**: Next.js 16 App Router, React 19, TypeScript 5, Tailwind CSS 4
|
||||
- **Backend**: Convex self-hosted (Coolify) with Better Auth for authentication
|
||||
- **UI**: shadcn/ui (radix-nova style), @hugeicons/react icons
|
||||
- **i18n**: next-intl v4 with locale-based routing (`/en`, `/pl`)
|
||||
- **State**: React Server Components + Client Components hybrid. No global state library.
|
||||
- **Auth**: Better Auth with email/password, HIBP plugin, Convex adapter
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/app/[locale]/layout.tsx` | Locale layout — validates locale, enables static rendering |
|
||||
| `src/app/layout.tsx` | Root layout — theme provider, i18n provider, dynamic `lang` |
|
||||
| `src/lib/auth-server.ts` | Server-side auth helpers (`isAuthenticated`, `getToken`) |
|
||||
| `src/lib/auth-client.ts` | Client-side auth client (`authClient.signIn.email(...)`) |
|
||||
| `src/i18n/routing.ts` | next-intl routing config (locales, defaultLocale, localePrefix) |
|
||||
| `src/i18n/request.ts` | Request config — reads locale from middleware, loads messages |
|
||||
| `src/proxy.ts` | next-intl middleware (Next.js 16 convention — was `middleware.ts`) |
|
||||
| `convex/auth.ts` | Better Auth configuration (plugins, password policy, HIBP) |
|
||||
| `convex/convex.config.ts` | Convex app definition |
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
app/[locale]/ # All pages live under locale segment
|
||||
layout.tsx # Locale validation + setRequestLocale
|
||||
page.tsx # Home page
|
||||
dashboard/page.tsx # Protected dashboard (server page + isAuthenticated)
|
||||
settings/page.tsx # Protected settings (server page + isAuthenticated)
|
||||
sign-in/page.tsx # Auth form with callbackURL support
|
||||
sign-up/page.tsx # Auth form with callbackURL support
|
||||
api/auth/[...all]/ # Better Auth API route
|
||||
components/
|
||||
ui/ # shadcn/ui primitives (button, card, field, etc.)
|
||||
auth/ # Auth-specific components
|
||||
settings/ # Settings-specific components
|
||||
core/ # App-wide components (ThemeChanger)
|
||||
lib/
|
||||
auth-server.ts # Server auth helpers
|
||||
auth-client.ts # Client auth client
|
||||
routes.ts # Route constants
|
||||
utils.ts # cn() and utilities
|
||||
env.ts # Validated environment variables (Zod)
|
||||
i18n/
|
||||
routing.ts # next-intl routing config
|
||||
request.ts # next-intl request config
|
||||
convex/
|
||||
auth.ts # Better Auth setup
|
||||
auth.config.ts # Convex auth config provider
|
||||
convex.config.ts # Convex app definition
|
||||
http.ts # Convex HTTP actions
|
||||
betterAuth/ # Better Auth Convex component
|
||||
messages/
|
||||
en.json # English translations
|
||||
pl.json # Polish translations
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
### File Naming
|
||||
|
||||
- **Components**: PascalCase (`PasswordChangeCard.tsx`, `AuthForm.tsx`)
|
||||
- **Pages**: `page.tsx` inside kebab-case directory (`sign-in/page.tsx`)
|
||||
- **Layouts**: `layout.tsx`
|
||||
- **Utilities**: camelCase (`auth-server.ts`, `utils.ts`)
|
||||
- **Constants**: `UPPER_SNAKE_CASE` for values, camelCase for files (`constants.ts`)
|
||||
|
||||
### Imports
|
||||
|
||||
```ts
|
||||
// Good
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { routes } from '@/lib/routes';
|
||||
|
||||
// Bad — never use @/src/components/
|
||||
import { Card } from '@/src/components/ui/card';
|
||||
```
|
||||
|
||||
### Auth Patterns
|
||||
|
||||
#### Server Component (check auth)
|
||||
|
||||
```tsx
|
||||
import { isAuthenticated } from '@/lib/auth-server';
|
||||
import { routes } from '@/lib/routes';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function ProtectedPage() {
|
||||
const authenticated = await isAuthenticated();
|
||||
if (!authenticated) {
|
||||
const searchParams = new URLSearchParams({ callbackURL: '/dashboard' });
|
||||
redirect(`${routes.public.signIn}?${searchParams.toString()}`);
|
||||
}
|
||||
// ... render protected content
|
||||
}
|
||||
```
|
||||
|
||||
#### Client Component (auth actions)
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
|
||||
async function handleSignIn(email: string, password: string) {
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
callbackURL: '/dashboard',
|
||||
});
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Password Change
|
||||
|
||||
```tsx
|
||||
const result = await authClient.changePassword({
|
||||
currentPassword: values.currentPassword,
|
||||
newPassword: values.newPassword,
|
||||
revokeOtherSessions: true,
|
||||
});
|
||||
```
|
||||
|
||||
### i18n Patterns
|
||||
|
||||
#### Server Component
|
||||
|
||||
```tsx
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export default async function Page() {
|
||||
const t = await getTranslations('Namespace');
|
||||
return <h1>{t('Title')}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Client Component
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export function Component() {
|
||||
const t = useTranslations('Namespace');
|
||||
return <h1>{t('Title')}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Adding Translations
|
||||
|
||||
1. Add keys to `messages/en.json` AND `messages/pl.json`
|
||||
2. Use namespace grouping (`AuthPage`, `DashboardPage`, `SettingsPage`)
|
||||
3. Never hardcode user-facing strings in components
|
||||
|
||||
### Route Constants
|
||||
|
||||
Always use `src/lib/routes.ts`:
|
||||
|
||||
```ts
|
||||
import { routes } from '@/lib/routes';
|
||||
|
||||
// Good
|
||||
<Link href={routes.private.settings}>Settings</Link>
|
||||
|
||||
// Bad
|
||||
<Link href="/settings">Settings</Link>
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
All env vars are validated at runtime in `src/lib/env.ts`. **Never read `process.env` directly** — import `env` instead:
|
||||
|
||||
```ts
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
// Good
|
||||
const url = env.NEXT_PUBLIC_CONVEX_URL;
|
||||
|
||||
// Bad
|
||||
const url = process.env.NEXT_PUBLIC_CONVEX_URL;
|
||||
```
|
||||
|
||||
## Anti-Patterns (NEVER DO)
|
||||
|
||||
### Auth
|
||||
|
||||
- ❌ **Never create custom Convex mutations for auth** — use Better Auth client API
|
||||
- ❌ **Never use `useTranslations` in async server components** — use `getTranslations` instead
|
||||
- ❌ **Never leave a private route unprotected** — always use `isAuthenticated()` + redirect
|
||||
- ❌ **Never hardcode `/sign-in` or `/dashboard`** — use `routes.public.signIn` / `routes.private.dashboard`
|
||||
|
||||
### i18n
|
||||
|
||||
- ❌ **Never read `Accept-Language` manually** — next-intl middleware handles locale detection
|
||||
- ❌ **Never hardcode `lang="en"`** — read from `x-next-intl-locale` header
|
||||
- ❌ **Never forget to add both `en.json` and `pl.json`** — keep translations in sync
|
||||
|
||||
### Code Quality
|
||||
|
||||
- ❌ **Never use `as any`** — proper typing exists (see `convex/betterAuth/auth.ts`)
|
||||
- ❌ **Never use `@/src/components/`** — use `@/components/` directly
|
||||
- ❌ **Never mix `@ts-ignore` or `@ts-expect-error`** — fix the type error instead
|
||||
- ❌ **Never read `process.env` directly** — always use validated `env` from `src/lib/env.ts`
|
||||
|
||||
### Next.js 16 Specifics
|
||||
|
||||
- ❌ **Never use `middleware.ts`** — Next.js 16 uses `proxy.ts` instead
|
||||
- ❌ **Never use Turbopack** — it's broken in 16.2.1 (900% CPU spike). Use `pnpm dev --webpack`
|
||||
|
||||
## Adding a New Feature
|
||||
|
||||
### New Protected Page
|
||||
|
||||
1. Create directory under `src/app/[locale]/my-page/`
|
||||
2. Add `page.tsx` as async server component
|
||||
3. Call `isAuthenticated()` at the top
|
||||
4. Redirect to `routes.public.signIn` with `callbackURL` if unauthenticated
|
||||
5. Add route to `src/lib/routes.ts` if it's a major page
|
||||
6. Add translations to `messages/en.json` and `messages/pl.json`
|
||||
|
||||
### New UI Component
|
||||
|
||||
1. Check if shadcn/ui primitive exists first (`src/components/ui/`)
|
||||
2. If not, create in `src/components/my-feature/MyComponent.tsx`
|
||||
3. Use existing patterns: `cn()` for class merging, `Field`/`FieldGroup` for forms
|
||||
4. Export from barrel file if you create an index
|
||||
|
||||
### New Convex Function
|
||||
|
||||
1. Create in `convex/myFeature.ts`
|
||||
2. Use `query({ args: {}, handler: async (ctx) => { ... } })`
|
||||
3. Export and use via generated API
|
||||
4. Never put auth logic in Convex — Better Auth owns that
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `missing field functions` on `npx convex dev`
|
||||
|
||||
CLI and Convex backend image versions don't match. Upgrade the lower one.
|
||||
|
||||
### Locale not switching
|
||||
|
||||
Check `src/proxy.ts` matcher includes the route. Check browser has `NEXT_LOCALE` cookie.
|
||||
|
||||
### Auth redirect loop
|
||||
|
||||
Ensure `callbackURL` starts with `/`. Ensure `routes.public.signIn` doesn't itself require auth.
|
||||
|
||||
## External References
|
||||
|
||||
- [Next.js 16 Docs](https://nextjs.org/docs) — check `node_modules/next/dist/docs/` for exact APIs
|
||||
- [Convex Docs](https://docs.convex.dev/)
|
||||
- [Better Auth Docs](https://www.better-auth.com/)
|
||||
- [next-intl Docs](https://next-intl.dev/)
|
||||
|
|
|
|||
Loading…
Reference in a new issue