agentbrief 0.1.0
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/LICENSE +21 -0
- package/README.md +141 -0
- package/briefs/code-reviewer/brief.yaml +8 -0
- package/briefs/code-reviewer/knowledge/review-standards.md +32 -0
- package/briefs/code-reviewer/personality.md +19 -0
- package/briefs/code-reviewer/skills/architecture-review/SKILL.md +76 -0
- package/briefs/code-reviewer/skills/review-process/SKILL.md +41 -0
- package/briefs/code-reviewer/skills/verification/SKILL.md +47 -0
- package/briefs/data-analyst/brief.yaml +8 -0
- package/briefs/data-analyst/knowledge/metrics-reference.md +43 -0
- package/briefs/data-analyst/personality.md +23 -0
- package/briefs/data-analyst/skills/metrics-framework/SKILL.md +90 -0
- package/briefs/data-analyst/skills/sql-query-builder/SKILL.md +115 -0
- package/briefs/devops-sre/brief.yaml +12 -0
- package/briefs/devops-sre/knowledge/runbook.md +69 -0
- package/briefs/devops-sre/personality.md +18 -0
- package/briefs/devops-sre/skills/ci-cd-github-actions/SKILL.md +114 -0
- package/briefs/devops-sre/skills/monitoring-observability/SKILL.md +394 -0
- package/briefs/devops-sre/skills/systematic-debugging/SKILL.md +46 -0
- package/briefs/devops-sre/skills/verification/SKILL.md +47 -0
- package/briefs/frontend-design/brief.yaml +8 -0
- package/briefs/frontend-design/knowledge/design-principles.md +43 -0
- package/briefs/frontend-design/personality.md +19 -0
- package/briefs/frontend-design/skills/design-review-checklist/SKILL.md +151 -0
- package/briefs/frontend-design/skills/web-design-guidelines/SKILL.md +39 -0
- package/briefs/fullstack-dev/brief.yaml +9 -0
- package/briefs/fullstack-dev/personality.md +18 -0
- package/briefs/growth-engineer/brief.yaml +8 -0
- package/briefs/growth-engineer/knowledge/growth-framework.md +83 -0
- package/briefs/growth-engineer/personality.md +19 -0
- package/briefs/growth-engineer/skills/analytics-setup/SKILL.md +109 -0
- package/briefs/growth-engineer/skills/brainstorming/SKILL.md +55 -0
- package/briefs/growth-engineer/skills/content-strategy/SKILL.md +93 -0
- package/briefs/growth-engineer/skills/seo-audit/SKILL.md +412 -0
- package/briefs/growth-engineer/skills/seo-audit/evals/evals.json +136 -0
- package/briefs/growth-engineer/skills/seo-audit/references/ai-writing-detection.md +200 -0
- package/briefs/nextjs-fullstack/brief.yaml +12 -0
- package/briefs/nextjs-fullstack/knowledge/conventions.md +57 -0
- package/briefs/nextjs-fullstack/personality.md +19 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/SKILL.md +153 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/async-patterns.md +87 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/bundling.md +180 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/data-patterns.md +297 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/debug-tricks.md +105 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/directives.md +73 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/error-handling.md +227 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/file-conventions.md +140 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/font.md +245 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/functions.md +108 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/hydration-error.md +91 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/image.md +173 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/metadata.md +301 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/parallel-routes.md +287 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/route-handlers.md +146 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/rsc-boundaries.md +159 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/runtime-selection.md +39 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/scripts.md +141 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/self-hosting.md +371 -0
- package/briefs/nextjs-fullstack/skills/next-best-practices/suspense-boundaries.md +67 -0
- package/briefs/nextjs-fullstack/skills/tdd/SKILL.md +53 -0
- package/briefs/product-manager/brief.yaml +8 -0
- package/briefs/product-manager/knowledge/pm-toolkit.md +51 -0
- package/briefs/product-manager/personality.md +19 -0
- package/briefs/product-manager/skills/brainstorming/SKILL.md +55 -0
- package/briefs/product-manager/skills/specification/SKILL.md +76 -0
- package/briefs/qa-engineer/brief.yaml +11 -0
- package/briefs/qa-engineer/knowledge/testing-patterns.md +54 -0
- package/briefs/qa-engineer/personality.md +24 -0
- package/briefs/qa-engineer/skills/qa-test-and-fix/SKILL.md +101 -0
- package/briefs/qa-engineer/skills/regression-testing/SKILL.md +95 -0
- package/briefs/security-auditor/brief.yaml +12 -0
- package/briefs/security-auditor/knowledge/code-patterns.md +49 -0
- package/briefs/security-auditor/knowledge/owasp-cheatsheet.md +75 -0
- package/briefs/security-auditor/personality.md +23 -0
- package/briefs/security-auditor/skills/security-review/SKILL.md +29 -0
- package/briefs/security-auditor/skills/systematic-debugging/SKILL.md +46 -0
- package/briefs/security-auditor/skills/verification/SKILL.md +47 -0
- package/briefs/startup-builder/brief.yaml +8 -0
- package/briefs/startup-builder/knowledge/startup-phases.md +64 -0
- package/briefs/startup-builder/personality.md +18 -0
- package/briefs/startup-builder/skills/ceo-review/SKILL.md +95 -0
- package/briefs/startup-builder/skills/launch-strategy/SKILL.md +353 -0
- package/briefs/startup-builder/skills/launch-strategy/evals/evals.json +91 -0
- package/briefs/startup-builder/skills/tdd/SKILL.md +53 -0
- package/briefs/startup-builder/skills/verification/SKILL.md +47 -0
- package/briefs/startup-kit/brief.yaml +9 -0
- package/briefs/startup-kit/personality.md +18 -0
- package/briefs/tech-writer/brief.yaml +8 -0
- package/briefs/tech-writer/knowledge/style-guide.md +54 -0
- package/briefs/tech-writer/personality.md +19 -0
- package/briefs/tech-writer/skills/api-documentation/SKILL.md +390 -0
- package/briefs/tech-writer/skills/plan-and-execute/SKILL.md +54 -0
- package/briefs/tech-writer/skills/release-notes/SKILL.md +77 -0
- package/briefs/typescript-strict/brief.yaml +8 -0
- package/briefs/typescript-strict/knowledge/type-patterns.md +117 -0
- package/briefs/typescript-strict/personality.md +23 -0
- package/briefs/typescript-strict/skills/typescript-advanced-types/SKILL.md +717 -0
- package/dist/brief.d.ts +13 -0
- package/dist/brief.d.ts.map +1 -0
- package/dist/brief.js +90 -0
- package/dist/brief.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +180 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler.d.ts +25 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +253 -0
- package/dist/compiler.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +255 -0
- package/dist/index.js.map +1 -0
- package/dist/injector.d.ts +17 -0
- package/dist/injector.d.ts.map +1 -0
- package/dist/injector.js +76 -0
- package/dist/injector.js.map +1 -0
- package/dist/lock.d.ts +8 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +50 -0
- package/dist/lock.js.map +1 -0
- package/dist/resolver.d.ts +24 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +135 -0
- package/dist/resolver.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/registry.yaml +91 -0
- package/templates/default/brief.yaml +7 -0
- package/templates/default/knowledge/.gitkeep +0 -0
- package/templates/default/personality.md +12 -0
- package/templates/security/brief.yaml +6 -0
- package/templates/security/knowledge/.gitkeep +0 -0
- package/templates/security/personality.md +20 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Scripts
|
|
2
|
+
|
|
3
|
+
Loading third-party scripts in Next.js.
|
|
4
|
+
|
|
5
|
+
## Use next/script
|
|
6
|
+
|
|
7
|
+
Always use `next/script` instead of native `<script>` tags for better performance.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Bad: Native script tag
|
|
11
|
+
<script src="https://example.com/script.js"></script>
|
|
12
|
+
|
|
13
|
+
// Good: Next.js Script component
|
|
14
|
+
import Script from 'next/script'
|
|
15
|
+
|
|
16
|
+
<Script src="https://example.com/script.js" />
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Inline Scripts Need ID
|
|
20
|
+
|
|
21
|
+
Inline scripts require an `id` attribute for Next.js to track them.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// Bad: Missing id
|
|
25
|
+
<Script dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
|
|
26
|
+
|
|
27
|
+
// Good: Has id
|
|
28
|
+
<Script id="my-script" dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
|
|
29
|
+
|
|
30
|
+
// Good: Inline with id
|
|
31
|
+
<Script id="show-banner">
|
|
32
|
+
{`document.getElementById('banner').classList.remove('hidden')`}
|
|
33
|
+
</Script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Don't Put Script in Head
|
|
37
|
+
|
|
38
|
+
`next/script` should not be placed inside `next/head`. It handles its own positioning.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Bad: Script inside Head
|
|
42
|
+
import Head from 'next/head'
|
|
43
|
+
import Script from 'next/script'
|
|
44
|
+
|
|
45
|
+
<Head>
|
|
46
|
+
<Script src="/analytics.js" />
|
|
47
|
+
</Head>
|
|
48
|
+
|
|
49
|
+
// Good: Script outside Head
|
|
50
|
+
<Head>
|
|
51
|
+
<title>Page</title>
|
|
52
|
+
</Head>
|
|
53
|
+
<Script src="/analytics.js" />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Loading Strategies
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// afterInteractive (default) - Load after page is interactive
|
|
60
|
+
<Script src="/analytics.js" strategy="afterInteractive" />
|
|
61
|
+
|
|
62
|
+
// lazyOnload - Load during idle time
|
|
63
|
+
<Script src="/widget.js" strategy="lazyOnload" />
|
|
64
|
+
|
|
65
|
+
// beforeInteractive - Load before page is interactive (use sparingly)
|
|
66
|
+
// Only works in app/layout.tsx or pages/_document.js
|
|
67
|
+
<Script src="/critical.js" strategy="beforeInteractive" />
|
|
68
|
+
|
|
69
|
+
// worker - Load in web worker (experimental)
|
|
70
|
+
<Script src="/heavy.js" strategy="worker" />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Google Analytics
|
|
74
|
+
|
|
75
|
+
Use `@next/third-parties` instead of inline GA scripts.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Bad: Inline GA script
|
|
79
|
+
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" />
|
|
80
|
+
<Script id="ga-init">
|
|
81
|
+
{`window.dataLayer = window.dataLayer || [];
|
|
82
|
+
function gtag(){dataLayer.push(arguments);}
|
|
83
|
+
gtag('js', new Date());
|
|
84
|
+
gtag('config', 'G-XXXXX');`}
|
|
85
|
+
</Script>
|
|
86
|
+
|
|
87
|
+
// Good: Next.js component
|
|
88
|
+
import { GoogleAnalytics } from '@next/third-parties/google'
|
|
89
|
+
|
|
90
|
+
export default function Layout({ children }) {
|
|
91
|
+
return (
|
|
92
|
+
<html>
|
|
93
|
+
<body>{children}</body>
|
|
94
|
+
<GoogleAnalytics gaId="G-XXXXX" />
|
|
95
|
+
</html>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Google Tag Manager
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { GoogleTagManager } from '@next/third-parties/google'
|
|
104
|
+
|
|
105
|
+
export default function Layout({ children }) {
|
|
106
|
+
return (
|
|
107
|
+
<html>
|
|
108
|
+
<GoogleTagManager gtmId="GTM-XXXXX" />
|
|
109
|
+
<body>{children}</body>
|
|
110
|
+
</html>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Other Third-Party Scripts
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// YouTube embed
|
|
119
|
+
import { YouTubeEmbed } from '@next/third-parties/google'
|
|
120
|
+
|
|
121
|
+
<YouTubeEmbed videoid="dQw4w9WgXcQ" />
|
|
122
|
+
|
|
123
|
+
// Google Maps
|
|
124
|
+
import { GoogleMapsEmbed } from '@next/third-parties/google'
|
|
125
|
+
|
|
126
|
+
<GoogleMapsEmbed
|
|
127
|
+
apiKey="YOUR_API_KEY"
|
|
128
|
+
mode="place"
|
|
129
|
+
q="Brooklyn+Bridge,New+York,NY"
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Quick Reference
|
|
134
|
+
|
|
135
|
+
| Pattern | Issue | Fix |
|
|
136
|
+
|---------|-------|-----|
|
|
137
|
+
| `<script src="...">` | No optimization | Use `next/script` |
|
|
138
|
+
| `<Script>` without id | Can't track inline scripts | Add `id` attribute |
|
|
139
|
+
| `<Script>` inside `<Head>` | Wrong placement | Move outside Head |
|
|
140
|
+
| Inline GA/GTM scripts | No optimization | Use `@next/third-parties` |
|
|
141
|
+
| `strategy="beforeInteractive"` outside layout | Won't work | Only use in root layout |
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Self-Hosting Next.js
|
|
2
|
+
|
|
3
|
+
Deploy Next.js outside of Vercel with confidence.
|
|
4
|
+
|
|
5
|
+
## Quick Start: Standalone Output
|
|
6
|
+
|
|
7
|
+
For Docker or any containerized deployment, use standalone output:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// next.config.js
|
|
11
|
+
module.exports = {
|
|
12
|
+
output: 'standalone',
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This creates a minimal `standalone` folder with only production dependencies:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
.next/
|
|
20
|
+
├── standalone/
|
|
21
|
+
│ ├── server.js # Entry point
|
|
22
|
+
│ ├── node_modules/ # Only production deps
|
|
23
|
+
│ └── .next/ # Build output
|
|
24
|
+
└── static/ # Must be copied separately
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Docker Deployment
|
|
28
|
+
|
|
29
|
+
### Dockerfile
|
|
30
|
+
|
|
31
|
+
```dockerfile
|
|
32
|
+
FROM node:20-alpine AS base
|
|
33
|
+
|
|
34
|
+
# Install dependencies
|
|
35
|
+
FROM base AS deps
|
|
36
|
+
WORKDIR /app
|
|
37
|
+
COPY package.json package-lock.json* ./
|
|
38
|
+
RUN npm ci
|
|
39
|
+
|
|
40
|
+
# Build
|
|
41
|
+
FROM base AS builder
|
|
42
|
+
WORKDIR /app
|
|
43
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
44
|
+
COPY . .
|
|
45
|
+
RUN npm run build
|
|
46
|
+
|
|
47
|
+
# Production
|
|
48
|
+
FROM base AS runner
|
|
49
|
+
WORKDIR /app
|
|
50
|
+
|
|
51
|
+
ENV NODE_ENV=production
|
|
52
|
+
|
|
53
|
+
# Create non-root user
|
|
54
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
55
|
+
RUN adduser --system --uid 1001 nextjs
|
|
56
|
+
|
|
57
|
+
# Copy standalone output
|
|
58
|
+
COPY --from=builder /app/.next/standalone ./
|
|
59
|
+
COPY --from=builder /app/.next/static ./.next/static
|
|
60
|
+
COPY --from=builder /app/public ./public
|
|
61
|
+
|
|
62
|
+
USER nextjs
|
|
63
|
+
|
|
64
|
+
EXPOSE 3000
|
|
65
|
+
ENV PORT=3000
|
|
66
|
+
ENV HOSTNAME="0.0.0.0"
|
|
67
|
+
|
|
68
|
+
CMD ["node", "server.js"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Docker Compose
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
version: '3.8'
|
|
75
|
+
|
|
76
|
+
services:
|
|
77
|
+
web:
|
|
78
|
+
build: .
|
|
79
|
+
ports:
|
|
80
|
+
- "3000:3000"
|
|
81
|
+
environment:
|
|
82
|
+
- NODE_ENV=production
|
|
83
|
+
restart: unless-stopped
|
|
84
|
+
healthcheck:
|
|
85
|
+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
|
86
|
+
interval: 30s
|
|
87
|
+
timeout: 10s
|
|
88
|
+
retries: 3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## PM2 Deployment
|
|
92
|
+
|
|
93
|
+
For traditional server deployments:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// ecosystem.config.js
|
|
97
|
+
module.exports = {
|
|
98
|
+
apps: [{
|
|
99
|
+
name: 'nextjs',
|
|
100
|
+
script: '.next/standalone/server.js',
|
|
101
|
+
instances: 'max',
|
|
102
|
+
exec_mode: 'cluster',
|
|
103
|
+
env: {
|
|
104
|
+
NODE_ENV: 'production',
|
|
105
|
+
PORT: 3000,
|
|
106
|
+
},
|
|
107
|
+
}],
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build
|
|
113
|
+
pm2 start ecosystem.config.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## ISR and Cache Handlers
|
|
117
|
+
|
|
118
|
+
### The Problem
|
|
119
|
+
|
|
120
|
+
ISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:
|
|
121
|
+
|
|
122
|
+
- Instance A regenerates page → saves to its local disk
|
|
123
|
+
- Instance B serves stale page → doesn't see Instance A's cache
|
|
124
|
+
- Load balancer sends users to random instances → inconsistent content
|
|
125
|
+
|
|
126
|
+
### Solution: Custom Cache Handler
|
|
127
|
+
|
|
128
|
+
Next.js 14+ supports custom cache handlers for shared storage:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// next.config.js
|
|
132
|
+
module.exports = {
|
|
133
|
+
cacheHandler: require.resolve('./cache-handler.js'),
|
|
134
|
+
cacheMaxMemorySize: 0, // Disable in-memory cache
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Redis Cache Handler Example
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
// cache-handler.js
|
|
142
|
+
const Redis = require('ioredis');
|
|
143
|
+
|
|
144
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
145
|
+
const CACHE_PREFIX = 'nextjs:';
|
|
146
|
+
|
|
147
|
+
module.exports = class CacheHandler {
|
|
148
|
+
constructor(options) {
|
|
149
|
+
this.options = options;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async get(key) {
|
|
153
|
+
const data = await redis.get(CACHE_PREFIX + key);
|
|
154
|
+
if (!data) return null;
|
|
155
|
+
|
|
156
|
+
const parsed = JSON.parse(data);
|
|
157
|
+
return {
|
|
158
|
+
value: parsed.value,
|
|
159
|
+
lastModified: parsed.lastModified,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async set(key, data, ctx) {
|
|
164
|
+
const cacheData = {
|
|
165
|
+
value: data,
|
|
166
|
+
lastModified: Date.now(),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Set TTL based on revalidate option
|
|
170
|
+
if (ctx?.revalidate) {
|
|
171
|
+
await redis.setex(
|
|
172
|
+
CACHE_PREFIX + key,
|
|
173
|
+
ctx.revalidate,
|
|
174
|
+
JSON.stringify(cacheData)
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async revalidateTag(tags) {
|
|
182
|
+
// Implement tag-based invalidation
|
|
183
|
+
// This requires tracking which keys have which tags
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### S3 Cache Handler Example
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
// cache-handler.js
|
|
192
|
+
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
193
|
+
|
|
194
|
+
const s3 = new S3Client({ region: process.env.AWS_REGION });
|
|
195
|
+
const BUCKET = process.env.CACHE_BUCKET;
|
|
196
|
+
|
|
197
|
+
module.exports = class CacheHandler {
|
|
198
|
+
async get(key) {
|
|
199
|
+
try {
|
|
200
|
+
const response = await s3.send(new GetObjectCommand({
|
|
201
|
+
Bucket: BUCKET,
|
|
202
|
+
Key: `cache/${key}`,
|
|
203
|
+
}));
|
|
204
|
+
const body = await response.Body.transformToString();
|
|
205
|
+
return JSON.parse(body);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
if (err.name === 'NoSuchKey') return null;
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async set(key, data, ctx) {
|
|
213
|
+
await s3.send(new PutObjectCommand({
|
|
214
|
+
Bucket: BUCKET,
|
|
215
|
+
Key: `cache/${key}`,
|
|
216
|
+
Body: JSON.stringify({
|
|
217
|
+
value: data,
|
|
218
|
+
lastModified: Date.now(),
|
|
219
|
+
}),
|
|
220
|
+
ContentType: 'application/json',
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## What Works vs What Needs Setup
|
|
227
|
+
|
|
228
|
+
| Feature | Single Instance | Multi-Instance | Notes |
|
|
229
|
+
|---------|----------------|----------------|-------|
|
|
230
|
+
| SSR | Yes | Yes | No special setup |
|
|
231
|
+
| SSG | Yes | Yes | Built at deploy time |
|
|
232
|
+
| ISR | Yes | Needs cache handler | Filesystem cache breaks |
|
|
233
|
+
| Image Optimization | Yes | Yes | CPU-intensive, consider CDN |
|
|
234
|
+
| Middleware | Yes | Yes | Runs on Node.js |
|
|
235
|
+
| Edge Runtime | Limited | Limited | Some features Node-only |
|
|
236
|
+
| `revalidatePath/Tag` | Yes | Needs cache handler | Must share cache |
|
|
237
|
+
| `next/font` | Yes | Yes | Fonts bundled at build |
|
|
238
|
+
| Draft Mode | Yes | Yes | Cookie-based |
|
|
239
|
+
|
|
240
|
+
## Image Optimization
|
|
241
|
+
|
|
242
|
+
Next.js Image Optimization works out of the box but is CPU-intensive.
|
|
243
|
+
|
|
244
|
+
### Option 1: Built-in (Simple)
|
|
245
|
+
|
|
246
|
+
Works automatically, but consider:
|
|
247
|
+
- Set `deviceSizes` and `imageSizes` in config to limit variants
|
|
248
|
+
- Use `minimumCacheTTL` to reduce regeneration
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
// next.config.js
|
|
252
|
+
module.exports = {
|
|
253
|
+
images: {
|
|
254
|
+
minimumCacheTTL: 60 * 60 * 24, // 24 hours
|
|
255
|
+
deviceSizes: [640, 750, 1080, 1920], // Limit sizes
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Option 2: External Loader (Recommended for Scale)
|
|
261
|
+
|
|
262
|
+
Offload to Cloudinary, Imgix, or similar:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
// next.config.js
|
|
266
|
+
module.exports = {
|
|
267
|
+
images: {
|
|
268
|
+
loader: 'custom',
|
|
269
|
+
loaderFile: './lib/image-loader.js',
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
// lib/image-loader.js
|
|
276
|
+
export default function cloudinaryLoader({ src, width, quality }) {
|
|
277
|
+
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
|
|
278
|
+
return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Environment Variables
|
|
283
|
+
|
|
284
|
+
### Build-time vs Runtime
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
// Available at build time only (baked into bundle)
|
|
288
|
+
NEXT_PUBLIC_API_URL=https://api.example.com
|
|
289
|
+
|
|
290
|
+
// Available at runtime (server-side only)
|
|
291
|
+
DATABASE_URL=postgresql://...
|
|
292
|
+
API_SECRET=...
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Runtime Configuration
|
|
296
|
+
|
|
297
|
+
For truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
// app/api/config/route.ts
|
|
301
|
+
export async function GET() {
|
|
302
|
+
return Response.json({
|
|
303
|
+
apiUrl: process.env.API_URL,
|
|
304
|
+
features: process.env.FEATURES?.split(','),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## OpenNext: Serverless Without Vercel
|
|
310
|
+
|
|
311
|
+
[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npx create-sst@latest
|
|
315
|
+
# or
|
|
316
|
+
npx @opennextjs/aws build
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Supports:
|
|
320
|
+
- AWS Lambda + CloudFront
|
|
321
|
+
- Cloudflare Workers
|
|
322
|
+
- Netlify Functions
|
|
323
|
+
- Deno Deploy
|
|
324
|
+
|
|
325
|
+
## Health Check Endpoint
|
|
326
|
+
|
|
327
|
+
Always include a health check for load balancers:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
// app/api/health/route.ts
|
|
331
|
+
export async function GET() {
|
|
332
|
+
try {
|
|
333
|
+
// Optional: check database connection
|
|
334
|
+
// await db.$queryRaw`SELECT 1`;
|
|
335
|
+
|
|
336
|
+
return Response.json({ status: 'healthy' }, { status: 200 });
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return Response.json({ status: 'unhealthy' }, { status: 503 });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Pre-Deployment Checklist
|
|
344
|
+
|
|
345
|
+
1. **Build locally first**: `npm run build` - catch errors before deploy
|
|
346
|
+
2. **Test standalone output**: `node .next/standalone/server.js`
|
|
347
|
+
3. **Set `output: 'standalone'`** for Docker
|
|
348
|
+
4. **Configure cache handler** for multi-instance ISR
|
|
349
|
+
5. **Set `HOSTNAME="0.0.0.0"`** for containers
|
|
350
|
+
6. **Copy `public/` and `.next/static/`** - not included in standalone
|
|
351
|
+
7. **Add health check endpoint**
|
|
352
|
+
8. **Test ISR revalidation** after deployment
|
|
353
|
+
9. **Monitor memory usage** - Node.js defaults may need tuning
|
|
354
|
+
|
|
355
|
+
## Testing Cache Handler
|
|
356
|
+
|
|
357
|
+
**Critical**: Test your cache handler on every Next.js upgrade:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# Start multiple instances
|
|
361
|
+
PORT=3001 node .next/standalone/server.js &
|
|
362
|
+
PORT=3002 node .next/standalone/server.js &
|
|
363
|
+
|
|
364
|
+
# Trigger ISR revalidation
|
|
365
|
+
curl http://localhost:3001/api/revalidate?path=/posts
|
|
366
|
+
|
|
367
|
+
# Verify both instances see the update
|
|
368
|
+
curl http://localhost:3001/posts
|
|
369
|
+
curl http://localhost:3002/posts
|
|
370
|
+
# Should return identical content
|
|
371
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Suspense Boundaries
|
|
2
|
+
|
|
3
|
+
Client hooks that cause CSR bailout without Suspense boundaries.
|
|
4
|
+
|
|
5
|
+
## useSearchParams
|
|
6
|
+
|
|
7
|
+
Always requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Bad: Entire page becomes CSR
|
|
11
|
+
'use client'
|
|
12
|
+
|
|
13
|
+
import { useSearchParams } from 'next/navigation'
|
|
14
|
+
|
|
15
|
+
export default function SearchBar() {
|
|
16
|
+
const searchParams = useSearchParams()
|
|
17
|
+
return <div>Query: {searchParams.get('q')}</div>
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Good: Wrap in Suspense
|
|
23
|
+
import { Suspense } from 'react'
|
|
24
|
+
import SearchBar from './search-bar'
|
|
25
|
+
|
|
26
|
+
export default function Page() {
|
|
27
|
+
return (
|
|
28
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
29
|
+
<SearchBar />
|
|
30
|
+
</Suspense>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## usePathname
|
|
36
|
+
|
|
37
|
+
Requires Suspense boundary when route has dynamic parameters.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// In dynamic route [slug]
|
|
41
|
+
// Bad: No Suspense
|
|
42
|
+
'use client'
|
|
43
|
+
import { usePathname } from 'next/navigation'
|
|
44
|
+
|
|
45
|
+
export function Breadcrumb() {
|
|
46
|
+
const pathname = usePathname()
|
|
47
|
+
return <nav>{pathname}</nav>
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Good: Wrap in Suspense
|
|
53
|
+
<Suspense fallback={<BreadcrumbSkeleton />}>
|
|
54
|
+
<Breadcrumb />
|
|
55
|
+
</Suspense>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you use `generateStaticParams`, Suspense is optional.
|
|
59
|
+
|
|
60
|
+
## Quick Reference
|
|
61
|
+
|
|
62
|
+
| Hook | Suspense Required |
|
|
63
|
+
|------|-------------------|
|
|
64
|
+
| `useSearchParams()` | Yes |
|
|
65
|
+
| `usePathname()` | Yes (dynamic routes) |
|
|
66
|
+
| `useParams()` | No |
|
|
67
|
+
| `useRouter()` | No |
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd
|
|
3
|
+
description: Red-green-refactor cycle for test-driven development
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> Methodology from [obra/superpowers](https://github.com/obra/superpowers) (MIT)
|
|
7
|
+
|
|
8
|
+
# Test-Driven Development (TDD)
|
|
9
|
+
|
|
10
|
+
Iron law: **no production code without a preceding failing test.**
|
|
11
|
+
|
|
12
|
+
## The RED-GREEN-REFACTOR Cycle
|
|
13
|
+
|
|
14
|
+
### RED -- Write a Failing Test
|
|
15
|
+
|
|
16
|
+
1. Identify the next smallest behavior to implement.
|
|
17
|
+
2. Write a test that asserts that behavior.
|
|
18
|
+
3. Run the test suite. Confirm the new test **fails** for the right reason.
|
|
19
|
+
4. If it passes already, your test is not testing new behavior -- rewrite it.
|
|
20
|
+
|
|
21
|
+
### GREEN -- Make It Pass
|
|
22
|
+
|
|
23
|
+
1. Write the **minimum** production code to make the failing test pass.
|
|
24
|
+
2. Do not add extra logic, handle future cases, or optimize.
|
|
25
|
+
3. Run the test suite. All tests must be green.
|
|
26
|
+
4. If other tests broke, fix them before moving on.
|
|
27
|
+
|
|
28
|
+
### REFACTOR -- Clean Up
|
|
29
|
+
|
|
30
|
+
1. Look at the code you just wrote. Remove duplication, improve naming, simplify.
|
|
31
|
+
2. Run the test suite after every refactor step. Stay green.
|
|
32
|
+
3. Do not add new behavior during refactor -- that requires a new RED step.
|
|
33
|
+
|
|
34
|
+
## Practical Rules
|
|
35
|
+
|
|
36
|
+
- One assertion per test when possible. Focused tests give clearer failure messages.
|
|
37
|
+
- Name tests by behavior: `should reject expired tokens`, not `test_validate_3`.
|
|
38
|
+
- Keep the cycle short: aim for minutes per iteration, not hours.
|
|
39
|
+
- If you are stuck making a test pass, the step is too big. Write a simpler test first.
|
|
40
|
+
- Commit after each GREEN or REFACTOR step. Small commits are cheap insurance.
|
|
41
|
+
|
|
42
|
+
## When to Apply TDD
|
|
43
|
+
|
|
44
|
+
- New features: always.
|
|
45
|
+
- Bug fixes: write a test that reproduces the bug first, then fix.
|
|
46
|
+
- Refactoring existing untested code: add characterization tests before changing anything.
|
|
47
|
+
|
|
48
|
+
## Anti-patterns to Avoid
|
|
49
|
+
|
|
50
|
+
- Writing production code first and tests after (test-last is not TDD).
|
|
51
|
+
- Writing multiple tests before making any of them pass.
|
|
52
|
+
- Skipping the REFACTOR step -- tech debt accrues silently.
|
|
53
|
+
- Over-mocking: if your test has more mocks than assertions, rethink the design.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Product Management Toolkit
|
|
2
|
+
|
|
3
|
+
## Document Outputs
|
|
4
|
+
|
|
5
|
+
### PRD (Product Requirements Document)
|
|
6
|
+
- Problem statement: what user pain are we solving? What is the evidence?
|
|
7
|
+
- User stories with acceptance criteria
|
|
8
|
+
- Success metrics: what metric moves if this works? How much? By when?
|
|
9
|
+
- Out of scope: what we are explicitly NOT building
|
|
10
|
+
- Risks: technical, business, and user adoption risks with mitigation plans
|
|
11
|
+
|
|
12
|
+
### User Stories
|
|
13
|
+
- Format: "As a [user type], I want [goal] so that [benefit]"
|
|
14
|
+
- Each story has clear acceptance criteria
|
|
15
|
+
- Stories are small enough to complete in one sprint
|
|
16
|
+
- Include edge cases and error states in acceptance criteria
|
|
17
|
+
|
|
18
|
+
### Technical Spec Requests
|
|
19
|
+
- Provide enough context for engineering to estimate and design
|
|
20
|
+
- Do not prescribe the solution -- describe the problem and constraints
|
|
21
|
+
- Include performance requirements, scale expectations, and integration points
|
|
22
|
+
- Reference existing system architecture where relevant
|
|
23
|
+
|
|
24
|
+
### Prioritization (RICE Framework)
|
|
25
|
+
- **Reach**: How many users will this impact in a given time period?
|
|
26
|
+
- **Impact**: How much will it move the target metric? (3 = massive, 2 = high, 1 = medium, 0.5 = low, 0.25 = minimal)
|
|
27
|
+
- **Confidence**: How sure are we about reach and impact estimates? (100% = high, 80% = medium, 50% = low)
|
|
28
|
+
- **Effort**: How many person-weeks of work?
|
|
29
|
+
- **Score**: (Reach x Impact x Confidence) / Effort
|
|
30
|
+
|
|
31
|
+
### Roadmap Items
|
|
32
|
+
- Theme -> Epic -> Story hierarchy with clear sequencing rationale
|
|
33
|
+
- Time horizons: Now (committed), Next (planned), Later (exploratory)
|
|
34
|
+
- Dependencies and blockers identified
|
|
35
|
+
- Flexibility increases with distance -- further out items are less defined
|
|
36
|
+
|
|
37
|
+
## Decision Framework
|
|
38
|
+
|
|
39
|
+
1. **Start with the problem** -- what user pain are we solving? What is the evidence?
|
|
40
|
+
2. **Define success** -- what metric moves if this works? How much? By when?
|
|
41
|
+
3. **Scope ruthlessly** -- what is the smallest thing we can ship to test the hypothesis?
|
|
42
|
+
4. **Identify risks** -- technical, business, and user adoption risks. Mitigation plan for each.
|
|
43
|
+
5. **Make trade-offs explicit** -- document what we are choosing NOT to do and why
|
|
44
|
+
|
|
45
|
+
## Communication Guidelines
|
|
46
|
+
|
|
47
|
+
- Lead with the decision or recommendation, then supporting evidence
|
|
48
|
+
- Use structured formats: bullet points, tables, decision matrices
|
|
49
|
+
- Quantify impact wherever possible with specific numbers
|
|
50
|
+
- Tailor detail level to audience: executives get summary, engineers get specifics
|
|
51
|
+
- Status updates: what shipped, what we learned, what is next
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Role
|
|
2
|
+
|
|
3
|
+
You are a product manager. You define what to build, why to build it, and how to validate that it works. You think in user problems, not features. You communicate in structured documents that align engineering, design, and business stakeholders.
|
|
4
|
+
|
|
5
|
+
## Tone
|
|
6
|
+
|
|
7
|
+
- Lead with the conclusion, then the reasoning
|
|
8
|
+
- Use bullet points, not paragraphs
|
|
9
|
+
- Quantify whenever possible -- "reduces onboarding time from 5 min to 2 min" not "improves onboarding"
|
|
10
|
+
- Use visual aids (diagrams, wireframes, user flows) over text descriptions
|
|
11
|
+
|
|
12
|
+
## Constraints
|
|
13
|
+
|
|
14
|
+
- Never propose a feature without articulating the user problem it solves
|
|
15
|
+
- Never skip success metrics -- every feature needs a measurable outcome
|
|
16
|
+
- Never write a PRD longer than 2 pages for an MVP feature
|
|
17
|
+
- Always include "out of scope" to prevent scope creep
|
|
18
|
+
- Always reference user research, data, or competitive analysis -- not just intuition
|
|
19
|
+
- Always define trade-offs explicitly -- document what you are choosing NOT to do and why
|