Coding Standards
Sylla-specific standards for the Next.js 16 App Router codebase. All rules are settled and enforced in code review. Full rationale and code examples for every rule are in references/standards.md.
How to Apply This Skill
- For quick lookups (rule name, convention, where a file belongs), use the Quick Reference tables below.
- For rationale, code examples, or edge cases, consult
references/standards.md.
- When reviewing code, run through the Code Smell Checklist at the bottom of this file.
All work must satisfy readability, KISS, DRY, YAGNI, and security — specifically: no as any, mandatory institutionId scoping on every DB query, and Server Components by default.
Quick Reference
TypeScript
| Rule |
Standard |
| Object shapes, props, data models |
interface |
| Unions, intersections, mapped types |
type |
React.FC<T> |
Banned — type props with interface directly |
| Named type for trivial one-off shape |
Banned — use an inline type annotation instead |
| Return types on exported functions |
Required |
| Return types on internal helpers |
Inferred |
| Intentional absence (DB returns) |
null |
| Optional params |
undefined (use ?) |
| Null guards |
Optional chaining + nullish coalescing |
Functions & Components
| Rule |
Standard |
| Function syntax |
Arrow functions always |
function keyword |
Banned — except Next.js file-convention export default function |
| Single-component file |
export default |
| Multi-export file |
Named exports |
| Props type name |
interface ComponentNameProps |
| Short props list (<4) |
Destructure in signature |
| Long props list (4+) |
Named props param, destructure in body |
| Default values |
In destructure signature, not body |
Error Handling
| Rule |
Standard |
| Exported functions that can fail |
Wrap entire body in tryCatch, return Result<T> |
throw from exported functions |
Banned |
Consuming Result<T> |
Check result.data branch before use |
| Calling server actions from client |
No try/catch needed — server action is wrapped in tryCatch; handle Result<T> branches |
| React-query mutations |
Handle errors via onError callback, not try/catch in mutationFn |
Database
| Rule |
Standard |
| Query syntax |
Prefer db.query.* (relational API); use db.select() for inner joins / aggregations |
| Raw SQL |
Banned (except custom migrations) |
| Multiple raw Drizzle queries |
db.batch() — single round-trip |
| Independent async function calls |
Promise.all — not db.batch() |
institutionId on user/institution queries |
Required unless isSuperAdmin confirms admin |
Caching
| Rule |
Standard |
| Caching directive |
'use cache' at top of function body (NOT unstable_cache) |
| Cache tags |
cacheTag(CACHE_TAGS.X, ...) — always use CACHE_TAGS constant, never hardcode strings |
| Cache lifetime |
cacheLife('profile') — use named profiles from next.config.mjs, never hardcode durations |
| Invalidation in Server Actions |
updateTag(tag) — instant UI refresh |
| Invalidation in Route Handlers/jobs |
revalidateTag(tag, 'max') — 'max' expiry required |
unstable_cache |
Banned — fully migrated |
File & Naming
| Thing |
Convention |
| Files & folders |
kebab-case |
| Variables |
camelCase |
| Functions |
camelCase, verb-first |
| Components |
PascalCase |
| Interfaces & types |
PascalCase |
| Hooks |
use prefix |
| Boolean vars |
is, has, can prefix |
| Constants (all scopes) |
SCREAMING_SNAKE_CASE |
Barrel index.ts |
Avoid — direct imports via alias |
Where Things Live
| What |
Where |
| Reusable DB query |
src/db/queries/[domain].ts |
| Route-specific server action |
src/app/.../[route]/_actions.ts |
| Reusable server action (non-DB) |
src/actions/[feature].ts |
| React component |
src/components/[domain]/ |
| Custom hook |
src/hooks/use-[name].ts |
| Shared constants |
src/lib/constants.ts |
| Shared utility functions |
src/lib/utils/[domain].ts |
| Shared importable types (plain TS) |
src/lib/types/[domain].ts |
| Database-inferred types |
src/db/types.ts |
| Zod schemas (validation) |
src/schemas/[domain].ts |
| Component-specific types |
src/components/[domain]/types.ts |
| Ambient global declarations |
/types/[name].d.ts — never import directly |
React & Next.js
| Rule |
Standard |
| Async |
async/await always |
| Parallel non-DB fetches |
Promise.all |
| Parallel DB fetches |
db.batch() |
| Conditionals |
Early returns over nested ternaries |
| State that depends on previous |
Functional updater setX(prev => ...) |
page-content.tsx |
Use when page is client-driven but needs a one-time server data load |
Routes with top-level await |
Must have sibling loading.tsx |
| Page content width |
Set via PageLayout contentWidth prop ('narrow' | 'wide' | 'default') — avoid ad-hoc mx-auto max-w-* on page roots |
Code Smell Checklist
Before opening a PR, check:
1---2name: coding-standards3description: This skill should be used when the user asks about "coding standards", "code style", "how should I write this", "is this correct style", "what's the convention for", "interface vs type", "should I use React.FC", "how do we handle errors", "tryCatch pattern", "db query style", "arrow functions", "named vs default export", "how to structure this file", "barrel files", "institutionId", "multi-tenant", "page-content pattern", "naming conventions", "SCREAMING_SNAKE_CASE", "reviewing a PR", "page width", "content width", "PageLayout", "does this pass code review", or wants to know whether code follows Sylla's Next.js 16 repository standards.4---56# Coding Standards78Sylla-specific standards for the Next.js 16 App Router codebase. All rules are settled and enforced in code review. Full rationale and code examples for every rule are in **`references/standards.md`**.910## How to Apply This Skill1112- For quick lookups (rule name, convention, where a file belongs), use the Quick Reference tables below.13- For rationale, code examples, or edge cases, consult `references/standards.md`.14- When reviewing code, run through the Code Smell Checklist at the bottom of this file.1516---1718All work must satisfy readability, KISS, DRY, YAGNI, and security — specifically: no `as any`, mandatory `institutionId` scoping on every DB query, and Server Components by default.1920---2122## Quick Reference2324### TypeScript2526| Rule | Standard |27|------|----------|28| Object shapes, props, data models | `interface` |29| Unions, intersections, mapped types | `type` |30| `React.FC<T>` | Banned — type props with `interface` directly |31| Named type for trivial one-off shape | Banned — use an inline type annotation instead |32| Return types on exported functions | Required |33| Return types on internal helpers | Inferred |34| Intentional absence (DB returns) | `null` |35| Optional params | `undefined` (use `?`) |36| Null guards | Optional chaining + nullish coalescing |3738### Functions & Components3940| Rule | Standard |41|------|----------|42| Function syntax | Arrow functions always |43| `function` keyword | Banned — except Next.js file-convention `export default function` |44| Single-component file | `export default` |45| Multi-export file | Named exports |46| Props type name | `interface ComponentNameProps` |47| Short props list (<4) | Destructure in signature |48| Long props list (4+) | Named `props` param, destructure in body |49| Default values | In destructure signature, not body |5051### Error Handling5253| Rule | Standard |54|------|----------|55| Exported functions that can fail | Wrap entire body in `tryCatch`, return `Result<T>` |56| `throw` from exported functions | Banned |57| Consuming `Result<T>` | Check `result.data` branch before use |58| Calling server actions from client | No `try/catch` needed — server action is wrapped in `tryCatch`; handle `Result<T>` branches |59| React-query mutations | Handle errors via `onError` callback, not `try/catch` in `mutationFn` |6061### Database6263| Rule | Standard |64|------|----------|65| Query syntax | Prefer `db.query.*` (relational API); use `db.select()` for inner joins / aggregations |66| Raw SQL | Banned (except custom migrations) |67| Multiple raw Drizzle queries | `db.batch()` — single round-trip |68| Independent async function calls | `Promise.all` — not `db.batch()` |69| `institutionId` on user/institution queries | Required unless `isSuperAdmin` confirms admin |7071### Caching7273| Rule | Standard |74|------|----------|75| Caching directive | `'use cache'` at top of function body (NOT `unstable_cache`) |76| Cache tags | `cacheTag(CACHE_TAGS.X, ...)` — always use `CACHE_TAGS` constant, never hardcode strings |77| Cache lifetime | `cacheLife('profile')` — use named profiles from `next.config.mjs`, never hardcode durations |78| Invalidation in Server Actions | `updateTag(tag)` — instant UI refresh |79| Invalidation in Route Handlers/jobs | `revalidateTag(tag, 'max')` — `'max'` expiry required |80| `unstable_cache` | Banned — fully migrated |8182### File & Naming8384| Thing | Convention |85|-------|-----------|86| Files & folders | kebab-case |87| Variables | camelCase |88| Functions | camelCase, verb-first |89| Components | PascalCase |90| Interfaces & types | PascalCase |91| Hooks | `use` prefix |92| Boolean vars | `is`, `has`, `can` prefix |93| Constants (all scopes) | `SCREAMING_SNAKE_CASE` |94| Barrel `index.ts` | Avoid — direct imports via alias |9596### Where Things Live9798| What | Where |99|------|-------|100| Reusable DB query | `src/db/queries/[domain].ts` |101| Route-specific server action | `src/app/.../[route]/_actions.ts` |102| Reusable server action (non-DB) | `src/actions/[feature].ts` |103| React component | `src/components/[domain]/` |104| Custom hook | `src/hooks/use-[name].ts` |105| Shared constants | `src/lib/constants.ts` |106| Shared utility functions | `src/lib/utils/[domain].ts` |107| Shared importable types (plain TS) | `src/lib/types/[domain].ts` |108| Database-inferred types | `src/db/types.ts` |109| Zod schemas (validation) | `src/schemas/[domain].ts` |110| Component-specific types | `src/components/[domain]/types.ts` |111| Ambient global declarations | `/types/[name].d.ts` — never import directly |112113### React & Next.js114115| Rule | Standard |116|------|----------|117| Async | `async/await` always |118| Parallel non-DB fetches | `Promise.all` |119| Parallel DB fetches | `db.batch()` |120| Conditionals | Early returns over nested ternaries |121| State that depends on previous | Functional updater `setX(prev => ...)` |122| `page-content.tsx` | Use when page is client-driven but needs a one-time server data load |123| Routes with top-level `await` | Must have sibling `loading.tsx` |124| Page content width | Set via `PageLayout` `contentWidth` prop (`'narrow'` \| `'wide'` \| `'default'`) — avoid ad-hoc `mx-auto max-w-*` on page roots |125126---127128## Code Smell Checklist129130Before opening a PR, check:131132- [ ] Function >~40 lines — split it133- [ ] >3 levels of nesting — use early returns134- [ ] Magic number/string — extract to `SCREAMING_SNAKE_CASE` constant135- [ ] `as any` — find the correct type136- [ ] `type` for a named object shape — change to `interface`137- [ ] Named `interface`/`type` for a trivial shape used in one place — inline it138- [ ] `React.FC<T>` — remove it139- [ ] `function` keyword (non–file-convention) — convert to arrow140- [ ] Named export on single-component file — convert to default export141- [ ] `Promise.all` for DB queries — replace with `db.batch()`142- [ ] `throw` inside exported function — wrap in `tryCatch`143- [ ] Missing `institutionId` in DB query (and no `isSuperAdmin` check) — multi-tenant violation144- [ ] `middleware.ts` — must be `proxy.ts`145- [ ] Wrapper function that only calls through — delete it146- [ ] Barrel `index.ts` without justification — remove, use direct imports147- [ ] `interface`/`type` exported from a `'use server'` file — move to `src/lib/types/[domain].ts` (types) or `src/schemas/[domain].ts` (Zod schemas)148- [ ] Sequential `for` loop over independent async calls — replace with `Promise.all`149- [ ] `unstable_cache` usage — replace with `'use cache'` directive150- [ ] Hardcoded cache tag string — use `CACHE_TAGS` constant from `src/lib/constants.ts`151- [ ] `updateTag` called from Route Handler — will silently fail; use `revalidateTag(tag, 'max')` instead152- [ ] `revalidateTag` called from Server Action without `'max'` — use `updateTag` for instant refresh153- [ ] Cached function returning error fallback data — caches the error state; throw instead so cache is skipped154- [ ] Function appears computationally heavy or performs slow I/O (large DB queries, external API calls, expensive transforms) — ask if it should be wrapped with `'use cache'`155