@tekyzinc/gsd-t 2.45.11 → 2.50.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +26 -5
- package/bin/debug-ledger.js +193 -0
- package/bin/gsd-t.js +259 -1
- package/commands/gsd-t-complete-milestone.md +2 -1
- package/commands/gsd-t-debug.md +48 -2
- package/commands/gsd-t-doc-ripple.md +148 -0
- package/commands/gsd-t-execute.md +102 -5
- package/commands/gsd-t-help.md +25 -2
- package/commands/gsd-t-integrate.md +41 -1
- package/commands/gsd-t-qa.md +26 -5
- package/commands/gsd-t-quick.md +39 -1
- package/commands/gsd-t-test-sync.md +26 -1
- package/commands/gsd-t-verify.md +8 -2
- package/commands/gsd-t-wave.md +57 -0
- package/docs/GSD-T-README.md +84 -1
- package/docs/architecture.md +9 -1
- package/docs/framework-comparison-scorecard.md +160 -0
- package/docs/requirements.md +33 -0
- package/examples/rules/desktop.ini +2 -0
- package/package.json +2 -2
- package/templates/CLAUDE-global.md +82 -4
- package/templates/stacks/_security.md +243 -0
- package/templates/stacks/desktop.ini +2 -0
- package/templates/stacks/docker.md +202 -0
- package/templates/stacks/firebase.md +166 -0
- package/templates/stacks/flutter.md +205 -0
- package/templates/stacks/github-actions.md +201 -0
- package/templates/stacks/graphql.md +216 -0
- package/templates/stacks/neo4j.md +218 -0
- package/templates/stacks/nextjs.md +184 -0
- package/templates/stacks/node-api.md +196 -0
- package/templates/stacks/playwright.md +528 -0
- package/templates/stacks/postgresql.md +225 -0
- package/templates/stacks/python.md +243 -0
- package/templates/stacks/react-native.md +216 -0
- package/templates/stacks/react.md +293 -0
- package/templates/stacks/redux.md +193 -0
- package/templates/stacks/rest-api.md +202 -0
- package/templates/stacks/supabase.md +188 -0
- package/templates/stacks/tailwind.md +169 -0
- package/templates/stacks/typescript.md +176 -0
- package/templates/stacks/vite.md +176 -0
- package/templates/stacks/vue.md +189 -0
- package/templates/stacks/zustand.md +203 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# GraphQL Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Schema Design
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Schema-first design — define the schema, then implement resolvers
|
|
12
|
+
├── Use PascalCase for types: User, OrderItem
|
|
13
|
+
├── Use camelCase for fields: firstName, createdAt
|
|
14
|
+
├── Use UPPER_SNAKE_CASE for enum values: ACTIVE, PENDING_REVIEW
|
|
15
|
+
├── Input types for mutations: CreateUserInput, UpdateOrderInput
|
|
16
|
+
├── Every mutation returns the affected type (not just Boolean)
|
|
17
|
+
└── Add descriptions to all types and fields — they power documentation
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**GOOD**
|
|
21
|
+
```graphql
|
|
22
|
+
"""A registered user in the system."""
|
|
23
|
+
type User {
|
|
24
|
+
id: ID!
|
|
25
|
+
"""User's display name."""
|
|
26
|
+
displayName: String!
|
|
27
|
+
email: String!
|
|
28
|
+
role: UserRole!
|
|
29
|
+
orders(first: Int = 20, after: String): OrderConnection!
|
|
30
|
+
createdAt: DateTime!
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enum UserRole {
|
|
34
|
+
ADMIN
|
|
35
|
+
MEMBER
|
|
36
|
+
VIEWER
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input CreateUserInput {
|
|
40
|
+
displayName: String!
|
|
41
|
+
email: String!
|
|
42
|
+
role: UserRole = MEMBER
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. Query Design
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
MANDATORY:
|
|
52
|
+
├── Singular for single resource: user(id: ID!): User
|
|
53
|
+
├── Plural for collections: users(first: Int, after: String, filter: UserFilter): UserConnection!
|
|
54
|
+
├── Use connection pattern (Relay) for paginated lists
|
|
55
|
+
├── Accept filter input types — not individual filter args
|
|
56
|
+
├── NEVER return unbounded lists — always require pagination args
|
|
57
|
+
└── Nullable return for single lookups (user may not exist)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**GOOD**
|
|
61
|
+
```graphql
|
|
62
|
+
type Query {
|
|
63
|
+
user(id: ID!): User
|
|
64
|
+
users(first: Int = 20, after: String, filter: UserFilter): UserConnection!
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
input UserFilter {
|
|
68
|
+
role: UserRole
|
|
69
|
+
isActive: Boolean
|
|
70
|
+
search: String
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type UserConnection {
|
|
74
|
+
edges: [UserEdge!]!
|
|
75
|
+
pageInfo: PageInfo!
|
|
76
|
+
totalCount: Int!
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type UserEdge {
|
|
80
|
+
node: User!
|
|
81
|
+
cursor: String!
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type PageInfo {
|
|
85
|
+
hasNextPage: Boolean!
|
|
86
|
+
endCursor: String
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. Mutation Design
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
MANDATORY:
|
|
96
|
+
├── Verb-first naming: createUser, updateOrder, deleteComment
|
|
97
|
+
├── Accept a single input argument: createUser(input: CreateUserInput!): User!
|
|
98
|
+
├── Return the mutated object — not Boolean or generic status
|
|
99
|
+
├── Use union types for error handling (preferred) or throw errors
|
|
100
|
+
├── Validate inputs in resolvers — schema types are not sufficient
|
|
101
|
+
└── Mutations must be idempotent where possible
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**GOOD**
|
|
105
|
+
```graphql
|
|
106
|
+
type Mutation {
|
|
107
|
+
createUser(input: CreateUserInput!): CreateUserPayload!
|
|
108
|
+
updateUser(id: ID!, input: UpdateUserInput!): User!
|
|
109
|
+
deleteUser(id: ID!): DeleteUserPayload!
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type CreateUserPayload {
|
|
113
|
+
user: User
|
|
114
|
+
errors: [ValidationError!]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type ValidationError {
|
|
118
|
+
field: String!
|
|
119
|
+
message: String!
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 4. Resolver Patterns
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
MANDATORY:
|
|
129
|
+
├── Thin resolvers — delegate to service/data layer, don't put business logic in resolvers
|
|
130
|
+
├── Use DataLoader for N+1 prevention — batch and cache database lookups
|
|
131
|
+
├── Auth checks in resolvers or middleware — not in the service layer
|
|
132
|
+
├── Return null for not-found, throw for unauthorized/forbidden
|
|
133
|
+
└── Log errors with context (query name, variables, user ID)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**GOOD**
|
|
137
|
+
```typescript
|
|
138
|
+
const resolvers = {
|
|
139
|
+
Query: {
|
|
140
|
+
user: async (_, { id }, { dataSources, user }) => {
|
|
141
|
+
if (!user) throw new AuthenticationError('Must be logged in');
|
|
142
|
+
return dataSources.userService.getById(id);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
User: {
|
|
146
|
+
orders: (parent, args, { dataSources }) =>
|
|
147
|
+
dataSources.orderLoader.load({ userId: parent.id, ...args }),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 5. N+1 Prevention — DataLoader
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
MANDATORY:
|
|
158
|
+
├── Use DataLoader for any field that resolves per-item in a list
|
|
159
|
+
├── Create loaders per request (new instance in context factory)
|
|
160
|
+
├── Batch function receives array of keys, returns array of results in same order
|
|
161
|
+
└── Cache is request-scoped — not shared between requests
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**GOOD**
|
|
165
|
+
```typescript
|
|
166
|
+
const userLoader = new DataLoader<string, User>(async (ids) => {
|
|
167
|
+
const users = await db.query('SELECT * FROM users WHERE id = ANY($1)', [ids]);
|
|
168
|
+
const userMap = new Map(users.map(u => [u.id, u]));
|
|
169
|
+
return ids.map(id => userMap.get(id) ?? new Error(`User ${id} not found`));
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. Client-Side Patterns
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
MANDATORY:
|
|
179
|
+
├── Co-locate queries with components — query file next to the component
|
|
180
|
+
├── Use fragments for shared field selections
|
|
181
|
+
├── Name all operations: query GetUser, mutation CreateOrder
|
|
182
|
+
├── Use generated types (graphql-codegen) — NEVER manually type query results
|
|
183
|
+
├── Handle loading, error, and empty states for every query
|
|
184
|
+
└── Cache update after mutations: refetch or update cache directly
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 7. Anti-Patterns
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
NEVER:
|
|
193
|
+
├── Unbounded list queries without pagination
|
|
194
|
+
├── Business logic in resolvers — delegate to services
|
|
195
|
+
├── N+1 queries — use DataLoader
|
|
196
|
+
├── Anonymous operations (unnamed queries/mutations)
|
|
197
|
+
├── Deeply nested queries without depth limiting
|
|
198
|
+
├── Returning Boolean from mutations — return the affected type
|
|
199
|
+
├── Over-fetching: requesting all fields when you need two
|
|
200
|
+
└── Manually typing query results — use codegen
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## GraphQL Verification Checklist
|
|
206
|
+
|
|
207
|
+
- [ ] Schema-first with descriptions on all types/fields
|
|
208
|
+
- [ ] Connection pattern for paginated lists
|
|
209
|
+
- [ ] All mutations return affected type (not Boolean)
|
|
210
|
+
- [ ] DataLoader used for N+1 prevention
|
|
211
|
+
- [ ] Input validation in resolvers
|
|
212
|
+
- [ ] Auth checks in resolvers or middleware
|
|
213
|
+
- [ ] Operations named (query GetUser, not anonymous)
|
|
214
|
+
- [ ] Generated types via codegen
|
|
215
|
+
- [ ] Loading, error, empty states handled client-side
|
|
216
|
+
- [ ] Query depth limiting configured
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Neo4j Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Data Modeling
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Nodes represent entities (nouns): (:User), (:Product), (:Order)
|
|
12
|
+
├── Relationships represent verbs: -[:PURCHASED]->. -[:FOLLOWS]->
|
|
13
|
+
├── Node labels: PascalCase singular (User, BusinessType — not users)
|
|
14
|
+
├── Relationship types: UPPER_SNAKE_CASE (PURCHASED, WORKS_AT, BELONGS_TO)
|
|
15
|
+
├── Properties: camelCase (firstName, createdAt)
|
|
16
|
+
├── Store properties on the node or relationship that "owns" them
|
|
17
|
+
├── Relationships ALWAYS have a direction — even if queried bidirectionally
|
|
18
|
+
└── NEVER use relationships as nodes (reify only when the relationship needs its own relationships)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**GOOD**
|
|
22
|
+
```cypher
|
|
23
|
+
(:User {id: "u-123", name: "Jane", email: "jane@example.com"})
|
|
24
|
+
-[:PURCHASED {purchasedAt: datetime(), amount: 29.99}]->
|
|
25
|
+
(:Product {id: "p-456", name: "Widget", category: "Tools"})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Cypher Query Patterns
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
MANDATORY:
|
|
34
|
+
├── Use parameterized queries — NEVER string concatenation
|
|
35
|
+
├── Use MERGE for upserts — CREATE for guaranteed-new, MATCH for existing
|
|
36
|
+
├── Always LIMIT results on user-facing queries
|
|
37
|
+
├── Use OPTIONAL MATCH when the relationship may not exist
|
|
38
|
+
├── Use WITH to chain query stages — improves readability and performance
|
|
39
|
+
├── Prefer pattern comprehension over COLLECT + UNWIND for subqueries
|
|
40
|
+
└── Always specify relationship direction in MATCH patterns
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**BAD**
|
|
44
|
+
```cypher
|
|
45
|
+
MATCH (u:User) WHERE u.name = '${name}' RETURN u // injection risk!
|
|
46
|
+
MATCH (u:User)--(p:Product) RETURN u, p // no direction, no limit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**GOOD**
|
|
50
|
+
```cypher
|
|
51
|
+
MATCH (u:User {id: $userId})-[:PURCHASED]->(p:Product)
|
|
52
|
+
RETURN u.name, p.name, p.category
|
|
53
|
+
ORDER BY p.name
|
|
54
|
+
LIMIT 50
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 3. Indexing and Constraints
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
MANDATORY:
|
|
63
|
+
├── Unique constraint on all ID properties: CREATE CONSTRAINT FOR (u:User) REQUIRE u.id IS UNIQUE
|
|
64
|
+
├── Index properties used in WHERE and MATCH lookups
|
|
65
|
+
├── Composite indexes for multi-property lookups
|
|
66
|
+
├── Full-text indexes for search fields (name, description)
|
|
67
|
+
├── Use EXPLAIN and PROFILE to verify query plans use indexes
|
|
68
|
+
└── NEVER rely on full node scans for lookups
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**GOOD**
|
|
72
|
+
```cypher
|
|
73
|
+
// Unique constraint (also creates an index)
|
|
74
|
+
CREATE CONSTRAINT user_id_unique FOR (u:User) REQUIRE u.id IS UNIQUE;
|
|
75
|
+
|
|
76
|
+
// Property index for common lookups
|
|
77
|
+
CREATE INDEX user_email_idx FOR (u:User) ON (u.email);
|
|
78
|
+
|
|
79
|
+
// Composite index
|
|
80
|
+
CREATE INDEX order_status_date FOR (o:Order) ON (o.status, o.createdAt);
|
|
81
|
+
|
|
82
|
+
// Full-text index for search
|
|
83
|
+
CREATE FULLTEXT INDEX user_search FOR (u:User) ON EACH [u.name, u.email];
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. Transaction Management
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
MANDATORY:
|
|
92
|
+
├── Use explicit transactions for multi-statement operations
|
|
93
|
+
├── Keep transactions short — don't hold locks while doing external I/O
|
|
94
|
+
├── Read transactions for queries: session.executeRead()
|
|
95
|
+
├── Write transactions for mutations: session.executeWrite()
|
|
96
|
+
├── Handle transient errors with retry (the driver retries automatically in managed transactions)
|
|
97
|
+
└── Always close sessions after use
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**GOOD**
|
|
101
|
+
```typescript
|
|
102
|
+
const session = driver.session();
|
|
103
|
+
try {
|
|
104
|
+
const result = await session.executeRead(async (tx) => {
|
|
105
|
+
const res = await tx.run(
|
|
106
|
+
'MATCH (u:User {id: $userId})-[:PURCHASED]->(p:Product) RETURN p',
|
|
107
|
+
{ userId }
|
|
108
|
+
);
|
|
109
|
+
return res.records.map(r => r.get('p').properties);
|
|
110
|
+
});
|
|
111
|
+
return result;
|
|
112
|
+
} finally {
|
|
113
|
+
await session.close();
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5. Driver Configuration
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
MANDATORY:
|
|
123
|
+
├── Create driver once at startup — reuse across requests
|
|
124
|
+
├── Set maxConnectionPoolSize based on workload (default 100 is usually fine)
|
|
125
|
+
├── Set connectionAcquisitionTimeout (default 60s — lower for web apps)
|
|
126
|
+
├── Use bolt:// for direct, neo4j:// for routing (cluster)
|
|
127
|
+
├── Verify connectivity at startup: driver.verifyConnectivity()
|
|
128
|
+
└── Close driver on application shutdown: driver.close()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**GOOD**
|
|
132
|
+
```typescript
|
|
133
|
+
import neo4j from 'neo4j-driver';
|
|
134
|
+
|
|
135
|
+
const driver = neo4j.driver(
|
|
136
|
+
process.env.NEO4J_URI!,
|
|
137
|
+
neo4j.auth.basic(process.env.NEO4J_USER!, process.env.NEO4J_PASSWORD!),
|
|
138
|
+
{
|
|
139
|
+
maxConnectionPoolSize: 50,
|
|
140
|
+
connectionAcquisitionTimeout: 10000,
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await driver.verifyConnectivity();
|
|
145
|
+
|
|
146
|
+
// On shutdown:
|
|
147
|
+
process.on('SIGTERM', () => driver.close());
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 6. Performance
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
MANDATORY:
|
|
156
|
+
├── PROFILE queries during development to check plan and db hits
|
|
157
|
+
├── Use indexes — full node scans are O(n) and kill performance
|
|
158
|
+
├── Avoid variable-length paths without upper bound: -[:FOLLOWS*1..5]-> not -[:FOLLOWS*]->
|
|
159
|
+
├── Use LIMIT early in the query — not just at the end
|
|
160
|
+
├── Batch large writes (1000-5000 nodes per transaction)
|
|
161
|
+
└── Use APOC for batch operations when available
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**BAD** — unbounded variable-length path:
|
|
165
|
+
```cypher
|
|
166
|
+
MATCH (u:User)-[:FOLLOWS*]->(f:User) RETURN f // scans entire graph!
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**GOOD**
|
|
170
|
+
```cypher
|
|
171
|
+
MATCH (u:User {id: $userId})-[:FOLLOWS*1..3]->(f:User)
|
|
172
|
+
RETURN DISTINCT f.id, f.name
|
|
173
|
+
LIMIT 100
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 7. APOC Patterns
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
WHEN APOC IS AVAILABLE:
|
|
182
|
+
├── apoc.periodic.iterate for large batch operations
|
|
183
|
+
├── apoc.merge.node for conditional upserts with labels
|
|
184
|
+
├── apoc.path.expandConfig for complex traversals with filters
|
|
185
|
+
├── apoc.export.json for data dumps
|
|
186
|
+
└── Check APOC version compatibility with your Neo4j version
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 8. Anti-Patterns
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
NEVER:
|
|
195
|
+
├── String concatenation in Cypher — use parameters ($param)
|
|
196
|
+
├── Unbounded queries without LIMIT
|
|
197
|
+
├── Variable-length paths without upper bound (-[:REL*]->)
|
|
198
|
+
├── Missing indexes on lookup properties
|
|
199
|
+
├── Creating a new driver per request — reuse the driver
|
|
200
|
+
├── Storing large blobs in node properties — use external storage
|
|
201
|
+
├── Dense connected nodes (supernode > 100K relationships) without optimization
|
|
202
|
+
└── Using nodes where relationships should be used (and vice versa)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Neo4j Verification Checklist
|
|
208
|
+
|
|
209
|
+
- [ ] Parameterized queries only — no string concatenation
|
|
210
|
+
- [ ] Unique constraints on all ID properties
|
|
211
|
+
- [ ] Indexes on all WHERE/MATCH lookup properties
|
|
212
|
+
- [ ] All queries have LIMIT
|
|
213
|
+
- [ ] Variable-length paths have upper bounds
|
|
214
|
+
- [ ] Explicit read/write transactions
|
|
215
|
+
- [ ] Driver created once and reused
|
|
216
|
+
- [ ] Sessions closed after use
|
|
217
|
+
- [ ] PROFILE run on complex queries
|
|
218
|
+
- [ ] Node labels PascalCase, relationship types UPPER_SNAKE_CASE
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Next.js Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. App Router (Next.js 13+)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Use the App Router (app/) — not Pages Router (pages/) for new projects
|
|
12
|
+
├── Default to Server Components — add 'use client' ONLY when needed
|
|
13
|
+
├── 'use client' needed for: useState, useEffect, event handlers, browser APIs
|
|
14
|
+
├── Keep 'use client' boundary as low as possible — don't mark entire pages
|
|
15
|
+
└── NEVER import server-only code in client components
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**BAD** — marking the whole page as client:
|
|
19
|
+
```tsx
|
|
20
|
+
'use client'; // ← Unnecessary — only the button needs interactivity
|
|
21
|
+
export default function Page() {
|
|
22
|
+
return <div><h1>Static content</h1><LikeButton /></div>;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**GOOD** — push client boundary down:
|
|
27
|
+
```tsx
|
|
28
|
+
// app/page.tsx — Server Component (default)
|
|
29
|
+
export default function Page() {
|
|
30
|
+
return <div><h1>Static content</h1><LikeButton /></div>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// components/LikeButton.tsx
|
|
34
|
+
'use client';
|
|
35
|
+
export function LikeButton() {
|
|
36
|
+
const [liked, setLiked] = useState(false);
|
|
37
|
+
return <button onClick={() => setLiked(!liked)}>Like</button>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Data Fetching
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
MANDATORY:
|
|
47
|
+
├── Server Components: fetch directly (async component) — no useEffect
|
|
48
|
+
├── Client Components: React Query for server data — same as react.md Section 1
|
|
49
|
+
├── Use Route Handlers (app/api/) for API endpoints — not pages/api/
|
|
50
|
+
├── Set revalidate or cache options on server-side fetches
|
|
51
|
+
└── NEVER fetch from your own API routes in Server Components — call the function directly
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**BAD** — Server Component calling its own API:
|
|
55
|
+
```tsx
|
|
56
|
+
// app/page.tsx
|
|
57
|
+
const res = await fetch('http://localhost:3000/api/users'); // calling yourself!
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**GOOD** — call the data function directly:
|
|
61
|
+
```tsx
|
|
62
|
+
// app/page.tsx
|
|
63
|
+
import { getUsers } from '@/lib/data/users';
|
|
64
|
+
export default async function Page() {
|
|
65
|
+
const users = await getUsers();
|
|
66
|
+
return <UserList users={users} />;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 3. Route Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
MANDATORY:
|
|
76
|
+
├── app/{route}/page.tsx — page component
|
|
77
|
+
├── app/{route}/layout.tsx — shared layout (wraps child pages)
|
|
78
|
+
├── app/{route}/loading.tsx — Suspense fallback for the route
|
|
79
|
+
├── app/{route}/error.tsx — error boundary for the route ('use client')
|
|
80
|
+
├── app/{route}/not-found.tsx — 404 for the route
|
|
81
|
+
├── Group routes with (parentheses) for layout grouping: app/(auth)/login
|
|
82
|
+
└── Dynamic routes: app/users/[id]/page.tsx
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 4. Server Actions
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
MANDATORY (Next.js 14+):
|
|
91
|
+
├── Use Server Actions for form mutations — not client-side API calls
|
|
92
|
+
├── Mark with 'use server' at the top of the function or file
|
|
93
|
+
├── Validate ALL inputs with Zod — Server Actions are public endpoints
|
|
94
|
+
├── Return typed results — not raw responses
|
|
95
|
+
└── Revalidate cache after mutations: revalidatePath() or revalidateTag()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**GOOD**
|
|
99
|
+
```tsx
|
|
100
|
+
'use server';
|
|
101
|
+
import { z } from 'zod';
|
|
102
|
+
import { revalidatePath } from 'next/cache';
|
|
103
|
+
|
|
104
|
+
const schema = z.object({ name: z.string().min(2), email: z.string().email() });
|
|
105
|
+
|
|
106
|
+
export async function createUser(formData: FormData) {
|
|
107
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
108
|
+
if (!parsed.success) return { error: parsed.error.flatten() };
|
|
109
|
+
|
|
110
|
+
await db.user.create({ data: parsed.data });
|
|
111
|
+
revalidatePath('/users');
|
|
112
|
+
return { success: true };
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 5. Environment Variables
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
MANDATORY:
|
|
122
|
+
├── NEXT_PUBLIC_ prefix for client-exposed vars — all others are server-only
|
|
123
|
+
├── NEVER put secrets in NEXT_PUBLIC_ vars — they're in the client bundle
|
|
124
|
+
├── Access server-only vars in Server Components, Route Handlers, Server Actions
|
|
125
|
+
├── Validate env vars at startup with t3-env or manual checks
|
|
126
|
+
└── .env.local in .gitignore — commit .env.example with placeholder values
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 6. Metadata and SEO
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
MANDATORY:
|
|
135
|
+
├── Export metadata object or generateMetadata function from page.tsx
|
|
136
|
+
├── Every page needs title and description at minimum
|
|
137
|
+
├── Use template for consistent titles: { template: '%s | AppName' }
|
|
138
|
+
├── Set Open Graph and Twitter card metadata for shared pages
|
|
139
|
+
└── Add robots.txt and sitemap.xml via app/robots.ts and app/sitemap.ts
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 7. Middleware
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
WHEN NEEDED:
|
|
148
|
+
├── Use middleware.ts at project root for auth redirects, geolocation, headers
|
|
149
|
+
├── Keep middleware fast — it runs on EVERY request matching the matcher
|
|
150
|
+
├── Use matcher config to limit which routes trigger middleware
|
|
151
|
+
├── NEVER do heavy computation or database calls in middleware
|
|
152
|
+
└── Use NextResponse.next() to continue, NextResponse.redirect() to redirect
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 8. Anti-Patterns
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
NEVER:
|
|
161
|
+
├── 'use client' on entire pages when only a small part needs interactivity
|
|
162
|
+
├── useEffect + fetch in components when a Server Component would work
|
|
163
|
+
├── Fetching your own API routes from Server Components
|
|
164
|
+
├── Server Actions without input validation — they're public endpoints
|
|
165
|
+
├── Secrets in NEXT_PUBLIC_ env vars
|
|
166
|
+
├── getServerSideProps / getStaticProps (Pages Router) in App Router projects
|
|
167
|
+
├── Importing server-only modules (fs, db) in 'use client' components
|
|
168
|
+
└── Massive layouts that re-render on every navigation
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Next.js Verification Checklist
|
|
174
|
+
|
|
175
|
+
- [ ] App Router used (app/ directory)
|
|
176
|
+
- [ ] Server Components by default — 'use client' only where needed
|
|
177
|
+
- [ ] Client boundary pushed as low as possible
|
|
178
|
+
- [ ] Server fetches call functions directly — not own API routes
|
|
179
|
+
- [ ] Server Actions validate input with Zod
|
|
180
|
+
- [ ] Cache revalidated after mutations
|
|
181
|
+
- [ ] Every page has metadata (title + description)
|
|
182
|
+
- [ ] loading.tsx and error.tsx for each major route
|
|
183
|
+
- [ ] No secrets in NEXT_PUBLIC_ vars
|
|
184
|
+
- [ ] Middleware is fast and uses matcher config
|