@zigrivers/scaffold 3.6.0 → 3.8.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/README.md +127 -12
- package/content/knowledge/backend/backend-api-design.md +103 -0
- package/content/knowledge/backend/backend-architecture.md +100 -0
- package/content/knowledge/backend/backend-async-patterns.md +101 -0
- package/content/knowledge/backend/backend-auth-patterns.md +100 -0
- package/content/knowledge/backend/backend-conventions.md +105 -0
- package/content/knowledge/backend/backend-data-modeling.md +102 -0
- package/content/knowledge/backend/backend-deployment.md +100 -0
- package/content/knowledge/backend/backend-dev-environment.md +102 -0
- package/content/knowledge/backend/backend-observability.md +102 -0
- package/content/knowledge/backend/backend-project-structure.md +100 -0
- package/content/knowledge/backend/backend-requirements.md +103 -0
- package/content/knowledge/backend/backend-security.md +104 -0
- package/content/knowledge/backend/backend-testing.md +101 -0
- package/content/knowledge/backend/backend-worker-patterns.md +100 -0
- package/content/knowledge/cli/cli-architecture.md +101 -0
- package/content/knowledge/cli/cli-conventions.md +117 -0
- package/content/knowledge/cli/cli-dev-environment.md +121 -0
- package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
- package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
- package/content/knowledge/cli/cli-output-patterns.md +107 -0
- package/content/knowledge/cli/cli-project-structure.md +124 -0
- package/content/knowledge/cli/cli-requirements.md +101 -0
- package/content/knowledge/cli/cli-shell-integration.md +130 -0
- package/content/knowledge/cli/cli-testing.md +134 -0
- package/content/knowledge/library/library-api-design.md +306 -0
- package/content/knowledge/library/library-architecture.md +247 -0
- package/content/knowledge/library/library-bundling.md +244 -0
- package/content/knowledge/library/library-conventions.md +229 -0
- package/content/knowledge/library/library-dev-environment.md +220 -0
- package/content/knowledge/library/library-documentation.md +300 -0
- package/content/knowledge/library/library-project-structure.md +237 -0
- package/content/knowledge/library/library-requirements.md +173 -0
- package/content/knowledge/library/library-security.md +257 -0
- package/content/knowledge/library/library-testing.md +319 -0
- package/content/knowledge/library/library-type-definitions.md +284 -0
- package/content/knowledge/library/library-versioning.md +300 -0
- package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
- package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
- package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
- package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
- package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
- package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
- package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
- package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
- package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
- package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
- package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
- package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
- package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
- package/content/knowledge/web-app/web-app-architecture.md +116 -0
- package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
- package/content/knowledge/web-app/web-app-conventions.md +121 -0
- package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
- package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
- package/content/knowledge/web-app/web-app-deployment.md +134 -0
- package/content/knowledge/web-app/web-app-design-system.md +158 -0
- package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
- package/content/knowledge/web-app/web-app-observability.md +221 -0
- package/content/knowledge/web-app/web-app-project-structure.md +160 -0
- package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
- package/content/knowledge/web-app/web-app-requirements.md +112 -0
- package/content/knowledge/web-app/web-app-security.md +193 -0
- package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
- package/content/knowledge/web-app/web-app-testing.md +249 -0
- package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
- package/content/methodology/backend-overlay.yml +73 -0
- package/content/methodology/cli-overlay.yml +69 -0
- package/content/methodology/library-overlay.yml +67 -0
- package/content/methodology/mobile-app-overlay.yml +71 -0
- package/content/methodology/web-app-overlay.yml +79 -0
- package/dist/cli/commands/init.d.ts +21 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +261 -13
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +206 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +1392 -64
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +82 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +302 -1
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
- package/dist/core/assembly/overlay-loader.js +2 -1
- package/dist/core/assembly/overlay-loader.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +56 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/game-pipeline.test.js +1 -0
- package/dist/e2e/game-pipeline.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +16 -0
- package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
- package/dist/e2e/project-type-overlays.test.js +834 -0
- package/dist/e2e/project-type-overlays.test.js.map +1 -0
- package/dist/types/config.d.ts +19 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/wizard/questions.d.ts +27 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +142 -3
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +206 -8
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +21 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +27 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +1 -1
- package/dist/types/wizard.d.ts +0 -14
- package/dist/types/wizard.d.ts.map +0 -1
- package/dist/types/wizard.js +0 -2
- package/dist/types/wizard.js.map +0 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-dev-environment
|
|
3
|
+
description: Dev server configuration, HMR setup, API proxy, environment variables, Docker for local services, and browser devtools for web app development
|
|
4
|
+
topics: [web-app, dev-environment, vite, webpack, hmr, docker, debugging]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A fast, reliable local development environment is a force multiplier for the entire team. The goal is sub-second feedback loops for code changes and zero friction getting from a fresh checkout to a running app. Every minute spent fighting the dev environment is a minute not spent building the product.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
A fast web app dev environment targets sub-second feedback loops with HMR and zero-friction onboarding. Use Vite for new projects (or the framework's built-in server for Next.js). Configure an API proxy to avoid CORS issues. Validate environment variables at startup with a schema. Run local dependencies in Docker Compose.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Dev Server Choice: Vite vs webpack
|
|
16
|
+
|
|
17
|
+
For new projects in 2024 and beyond, use **Vite** unless you have a specific reason not to:
|
|
18
|
+
|
|
19
|
+
- **Vite**: Native ESM dev server, near-instant startup regardless of project size, esbuild-powered transforms (10–100x faster than Babel), first-class support for React, Vue, Svelte, and TypeScript out of the box.
|
|
20
|
+
- **webpack (via Create React App, Next.js with webpack, or custom)**: Slower cold starts and HMR, but battle-tested and required by some frameworks. Next.js 13+ defaults to Turbopack (Rust-based, approaching Vite speed).
|
|
21
|
+
|
|
22
|
+
If you are on Next.js, you get the dev server bundled with the framework. Do not fight the framework's built-in tooling — configure it via `next.config.ts`, not by ejecting or customizing webpack directly unless absolutely necessary.
|
|
23
|
+
|
|
24
|
+
### HMR Configuration
|
|
25
|
+
|
|
26
|
+
Hot Module Replacement keeps the page live-updating without full reloads. Ensure it is working correctly:
|
|
27
|
+
|
|
28
|
+
- With Vite: HMR works out of the box. If it stops working after upgrading, check `server.hmr` in `vite.config.ts`. Common issue: HMR fails silently when a module has a circular dependency.
|
|
29
|
+
- With React: Install `@vitejs/plugin-react` (uses Babel with Fast Refresh) or `@vitejs/plugin-react-swc` (uses SWC, 3–5x faster). Fast Refresh preserves component state on save — test that it works for your components.
|
|
30
|
+
- Watch for HMR performance degradation in large projects: if HMR is slow, profile with `vite --debug hmr`. The usual cause is a large shared module being invalidated on every change.
|
|
31
|
+
|
|
32
|
+
### API Proxy Configuration
|
|
33
|
+
|
|
34
|
+
During development, your app's origin is `localhost:3000` but your API backend is `localhost:8000`. Avoid CORS issues and hardcoded localhost URLs by configuring a dev proxy:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// vite.config.ts
|
|
38
|
+
export default defineConfig({
|
|
39
|
+
server: {
|
|
40
|
+
proxy: {
|
|
41
|
+
"/api": {
|
|
42
|
+
target: "http://localhost:8000",
|
|
43
|
+
changeOrigin: true,
|
|
44
|
+
rewrite: (path) => path.replace(/^\/api/, ""),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This means your frontend code always calls `/api/users` and the proxy handles routing to the backend. The same relative URL works in production when the API is behind the same domain. Never hardcode `http://localhost:8000` in application code.
|
|
52
|
+
|
|
53
|
+
### Environment Variables
|
|
54
|
+
|
|
55
|
+
Use `.env` files for environment-specific configuration. Establish clear conventions:
|
|
56
|
+
|
|
57
|
+
- `.env.example` — committed, documents all variables with dummy values
|
|
58
|
+
- `.env.local` — gitignored, developer-specific overrides (actual API keys, local service URLs)
|
|
59
|
+
- `.env.development` — gitignored, shared dev defaults (can be committed if values are non-sensitive)
|
|
60
|
+
- `.env.production` — injected by CI/CD, never committed
|
|
61
|
+
|
|
62
|
+
Validate all required variables at startup. An app that silently uses `undefined` for a missing API URL is worse than one that crashes immediately with a clear error:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// lib/env.ts — validate at module load time
|
|
66
|
+
import { z } from "zod";
|
|
67
|
+
|
|
68
|
+
const envSchema = z.object({
|
|
69
|
+
VITE_API_URL: z.string().url(),
|
|
70
|
+
VITE_FEATURE_FLAGS: z.string().default(""),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const env = envSchema.parse(import.meta.env);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Docker for Local Services
|
|
77
|
+
|
|
78
|
+
Run local dependencies (databases, caches, queues, email servers) in Docker to keep developer machines clean and ensure consistency:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
# docker-compose.yml
|
|
82
|
+
services:
|
|
83
|
+
postgres:
|
|
84
|
+
image: postgres:16-alpine
|
|
85
|
+
ports: ["5432:5432"]
|
|
86
|
+
environment:
|
|
87
|
+
POSTGRES_DB: myapp_dev
|
|
88
|
+
POSTGRES_USER: dev
|
|
89
|
+
POSTGRES_PASSWORD: dev
|
|
90
|
+
volumes:
|
|
91
|
+
- postgres_data:/var/lib/postgresql/data
|
|
92
|
+
|
|
93
|
+
redis:
|
|
94
|
+
image: redis:7-alpine
|
|
95
|
+
ports: ["6379:6379"]
|
|
96
|
+
|
|
97
|
+
mailpit:
|
|
98
|
+
image: axllent/mailpit
|
|
99
|
+
ports:
|
|
100
|
+
- "1025:1025" # SMTP
|
|
101
|
+
- "8025:8025" # Web UI
|
|
102
|
+
|
|
103
|
+
volumes:
|
|
104
|
+
postgres_data:
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Document the setup in the README: `docker compose up -d` starts all services. Never require developers to install PostgreSQL, Redis, or other services directly on their machines.
|
|
108
|
+
|
|
109
|
+
### Getting to Zero-Friction Setup
|
|
110
|
+
|
|
111
|
+
Document the new developer setup flow and time it. Target under 10 minutes from fresh checkout to running app. Anything over 15 minutes will be skipped and developers will use workarounds.
|
|
112
|
+
|
|
113
|
+
Minimum `package.json` scripts:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"scripts": {
|
|
118
|
+
"dev": "vite",
|
|
119
|
+
"dev:full": "docker compose up -d && vite",
|
|
120
|
+
"build": "tsc && vite build",
|
|
121
|
+
"preview": "vite preview",
|
|
122
|
+
"test": "vitest",
|
|
123
|
+
"test:ui": "vitest --ui",
|
|
124
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
125
|
+
"typecheck": "tsc --noEmit"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Include a `Makefile` or `justfile` for multi-step setup commands that developers run once (`make setup` installs deps, configures git hooks, copies `.env.example`).
|
|
131
|
+
|
|
132
|
+
### Browser Devtools Configuration
|
|
133
|
+
|
|
134
|
+
Install and configure browser extensions that accelerate debugging:
|
|
135
|
+
|
|
136
|
+
- **React Developer Tools**: Component tree inspection, props/state browser, profiler for render performance.
|
|
137
|
+
- **Redux DevTools** (if using Redux): Action/state history, time-travel debugging.
|
|
138
|
+
- **Vite plugin for browser devtools**: Install `vite-plugin-inspect` to visualize the Vite plugin pipeline and module graph.
|
|
139
|
+
|
|
140
|
+
Configure source maps in development (`devtool: "eval-source-map"` in webpack or `sourcemap: true` in Vite) so stack traces point to TypeScript source, not compiled JavaScript. Without this, debugging is nearly impossible.
|
|
141
|
+
|
|
142
|
+
### TypeScript Strict Mode
|
|
143
|
+
|
|
144
|
+
Enable strict mode from day one in `tsconfig.json`:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"compilerOptions": {
|
|
149
|
+
"strict": true,
|
|
150
|
+
"noUncheckedIndexedAccess": true,
|
|
151
|
+
"exactOptionalPropertyTypes": true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Disabling strict mode to make TypeScript "less annoying" creates a false sense of safety. The errors strict mode surfaces are real bugs. Fix them instead of disabling the check.
|
|
157
|
+
|
|
158
|
+
### VS Code Workspace Settings
|
|
159
|
+
|
|
160
|
+
Commit a `.vscode/settings.json` with recommended settings so all developers use consistent formatting:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"editor.formatOnSave": true,
|
|
165
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
166
|
+
"editor.codeActionsOnSave": {
|
|
167
|
+
"source.fixAll.eslint": true
|
|
168
|
+
},
|
|
169
|
+
"typescript.preferences.importModuleSpecifier": "non-relative"
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Commit `.vscode/extensions.json` with recommended extensions. Developers are prompted to install them on workspace open.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-observability
|
|
3
|
+
description: Real User Monitoring, Core Web Vitals tracking, error tracking with Sentry, CDN analytics, custom performance marks, and performance regression alerting
|
|
4
|
+
topics: [web-app, observability, rum, core-web-vitals, sentry, performance, monitoring]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You cannot improve what you cannot measure. Frontend observability is often treated as an afterthought — added after users start complaining — but by then you have no baseline to regress against and no historical data to understand when performance degraded or errors started. Real User Monitoring (RUM) captures the experience of your actual users on their actual devices and networks, which lab-based tests like Lighthouse cannot replicate. Core Web Vitals are Google's standardized metrics for page experience and directly influence search ranking.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
### Real User Monitoring (RUM)
|
|
12
|
+
|
|
13
|
+
RUM collects performance and behavioral data from real user sessions in production. Unlike synthetic monitoring (scheduled tests from a data center), RUM reflects the diversity of real-world conditions: slow Android devices, congested mobile networks, aggressive ad blockers, and geographically distant users.
|
|
14
|
+
|
|
15
|
+
**Key RUM metrics:**
|
|
16
|
+
|
|
17
|
+
- **Core Web Vitals** — Google's page experience signals (see below)
|
|
18
|
+
- **Time to First Byte (TTFB)** — server response latency
|
|
19
|
+
- **First Contentful Paint (FCP)** — when the first content is painted
|
|
20
|
+
- **Time to Interactive (TTI)** — when the page is reliably interactive
|
|
21
|
+
- **Custom marks** — application-specific milestones (e.g., "dashboard data loaded")
|
|
22
|
+
- **Error rate** — JavaScript exceptions per session
|
|
23
|
+
- **Rage clicks** — repeated clicks on non-interactive elements (UX frustration signal)
|
|
24
|
+
- **Dead clicks** — clicks on elements that produce no response
|
|
25
|
+
|
|
26
|
+
**RUM tools:** Vercel Analytics, Datadog RUM, New Relic Browser, Sentry Performance, web-vitals library (self-hosted reporting).
|
|
27
|
+
|
|
28
|
+
### Core Web Vitals
|
|
29
|
+
|
|
30
|
+
Google's three Core Web Vitals measure loading, interactivity, and visual stability:
|
|
31
|
+
|
|
32
|
+
| Metric | Measures | Good | Needs Improvement | Poor |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| **LCP** (Largest Contentful Paint) | Load performance | ≤ 2.5s | 2.5–4s | > 4s |
|
|
35
|
+
| **INP** (Interaction to Next Paint) | Responsiveness | ≤ 200ms | 200–500ms | > 500ms |
|
|
36
|
+
| **CLS** (Cumulative Layout Shift) | Visual stability | ≤ 0.1 | 0.1–0.25 | > 0.25 |
|
|
37
|
+
|
|
38
|
+
**Common LCP causes and fixes:**
|
|
39
|
+
- Unoptimized hero images → use `srcset`, WebP/AVIF, CDN, `loading="eager"`, `fetchpriority="high"`
|
|
40
|
+
- Render-blocking CSS/fonts → inline critical CSS, use `font-display: swap`
|
|
41
|
+
- Slow TTFB → edge caching, CDN, server-side optimization
|
|
42
|
+
|
|
43
|
+
**Common CLS causes and fixes:**
|
|
44
|
+
- Images without explicit `width`/`height` attributes → always set dimensions
|
|
45
|
+
- Late-loading ads or embeds → reserve space with aspect-ratio boxes
|
|
46
|
+
- Custom fonts causing text reflow → use `font-display: optional` or `size-adjust`
|
|
47
|
+
- Dynamic content injected above viewport content → append below, not prepend above
|
|
48
|
+
|
|
49
|
+
**INP (replaced FID in 2024):** Measures the responsiveness of all user interactions, not just the first. Long JavaScript tasks (>50ms) block the main thread and inflate INP. Use `scheduler.postTask()`, web workers, and code splitting to break up long tasks.
|
|
50
|
+
|
|
51
|
+
### Error Tracking with Sentry
|
|
52
|
+
|
|
53
|
+
Sentry captures JavaScript exceptions, stack traces, user context, and breadcrumbs (the sequence of events leading to the error). Configure it to filter noise and surface actionable errors.
|
|
54
|
+
|
|
55
|
+
**Critical Sentry configuration:**
|
|
56
|
+
- Source maps for readable stack traces (never ship source maps publicly — upload to Sentry only)
|
|
57
|
+
- User context (attach user ID, plan tier — never PII like email without consent)
|
|
58
|
+
- Release tracking to correlate error spikes with deployments
|
|
59
|
+
- Sampling rate: 100% for errors, 10–20% for performance transactions in high-traffic apps
|
|
60
|
+
|
|
61
|
+
### Performance Regression Alerting
|
|
62
|
+
|
|
63
|
+
The goal is to catch regressions before they reach users or before they compound. Alert on:
|
|
64
|
+
- LCP p75 (75th percentile) increases by more than 300ms from the rolling 7-day average
|
|
65
|
+
- Error rate increases by more than 0.5% from the rolling 24-hour baseline
|
|
66
|
+
- Any new error type appearing in production for the first time
|
|
67
|
+
|
|
68
|
+
## Deep Guidance
|
|
69
|
+
|
|
70
|
+
### Web Vitals Collection
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// web-vitals library — collect and report Core Web Vitals
|
|
74
|
+
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';
|
|
75
|
+
|
|
76
|
+
function sendToAnalytics(metric: any) {
|
|
77
|
+
// Batch metrics to avoid sending too many beacons
|
|
78
|
+
const body = JSON.stringify({
|
|
79
|
+
name: metric.name,
|
|
80
|
+
value: metric.value,
|
|
81
|
+
rating: metric.rating, // 'good', 'needs-improvement', 'poor'
|
|
82
|
+
id: metric.id,
|
|
83
|
+
navigationType: metric.navigationType,
|
|
84
|
+
url: window.location.pathname,
|
|
85
|
+
// Attach context for segmentation
|
|
86
|
+
connectionType: (navigator as any).connection?.effectiveType,
|
|
87
|
+
deviceMemory: (navigator as any).deviceMemory,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// sendBeacon is non-blocking and survives page unload
|
|
91
|
+
if (navigator.sendBeacon) {
|
|
92
|
+
navigator.sendBeacon('/api/vitals', body);
|
|
93
|
+
} else {
|
|
94
|
+
fetch('/api/vitals', { method: 'POST', body, keepalive: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Register all Core Web Vitals + FCP + TTFB
|
|
99
|
+
onLCP(sendToAnalytics);
|
|
100
|
+
onINP(sendToAnalytics);
|
|
101
|
+
onCLS(sendToAnalytics);
|
|
102
|
+
onFCP(sendToAnalytics);
|
|
103
|
+
onTTFB(sendToAnalytics);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Always use `navigator.sendBeacon` for analytics reporting — it queues the request to be sent after the page unloads without blocking navigation.
|
|
107
|
+
|
|
108
|
+
### Custom Performance Marks
|
|
109
|
+
|
|
110
|
+
Use the User Timing API to measure application-specific milestones that browser APIs cannot capture:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Mark application-level performance milestones
|
|
114
|
+
class PerformanceTracker {
|
|
115
|
+
private marks: Map<string, number> = new Map();
|
|
116
|
+
|
|
117
|
+
mark(name: string): void {
|
|
118
|
+
performance.mark(name);
|
|
119
|
+
this.marks.set(name, performance.now());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
measure(name: string, startMark: string, endMark?: string): number {
|
|
123
|
+
const end = endMark || name + '-end';
|
|
124
|
+
if (!endMark) this.mark(end);
|
|
125
|
+
|
|
126
|
+
const measure = performance.measure(name, startMark, end);
|
|
127
|
+
const duration = measure.duration;
|
|
128
|
+
|
|
129
|
+
// Report to RUM
|
|
130
|
+
this.report({ name, duration });
|
|
131
|
+
return duration;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private report(metric: { name: string; duration: number }): void {
|
|
135
|
+
navigator.sendBeacon?.('/api/perf', JSON.stringify(metric));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const perf = new PerformanceTracker();
|
|
140
|
+
|
|
141
|
+
// Usage in application code
|
|
142
|
+
async function loadDashboard() {
|
|
143
|
+
perf.mark('dashboard-fetch-start');
|
|
144
|
+
const data = await fetchDashboardData();
|
|
145
|
+
perf.mark('dashboard-fetch-end');
|
|
146
|
+
|
|
147
|
+
perf.measure('dashboard-fetch', 'dashboard-fetch-start', 'dashboard-fetch-end');
|
|
148
|
+
|
|
149
|
+
perf.mark('dashboard-render-start');
|
|
150
|
+
renderDashboard(data);
|
|
151
|
+
perf.mark('dashboard-render-end');
|
|
152
|
+
|
|
153
|
+
perf.measure('dashboard-render', 'dashboard-render-start', 'dashboard-render-end');
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
These custom marks appear in Chrome DevTools Performance panel and can be reported to your RUM backend for tracking.
|
|
158
|
+
|
|
159
|
+
### Sentry Setup for Next.js
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// sentry.client.config.ts
|
|
163
|
+
import * as Sentry from '@sentry/nextjs';
|
|
164
|
+
|
|
165
|
+
Sentry.init({
|
|
166
|
+
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
167
|
+
environment: process.env.NEXT_PUBLIC_ENV,
|
|
168
|
+
|
|
169
|
+
// Performance monitoring sample rate — adjust based on traffic volume
|
|
170
|
+
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
|
|
171
|
+
|
|
172
|
+
// Replay for error sessions only (privacy-conscious default)
|
|
173
|
+
replaysOnErrorSampleRate: 1.0,
|
|
174
|
+
replaysSessionSampleRate: 0.0,
|
|
175
|
+
|
|
176
|
+
// Filter noise — don't track network errors from ad blockers
|
|
177
|
+
ignoreErrors: [
|
|
178
|
+
'ResizeObserver loop limit exceeded',
|
|
179
|
+
'Non-Error promise rejection captured',
|
|
180
|
+
/Loading chunk \d+ failed/,
|
|
181
|
+
],
|
|
182
|
+
|
|
183
|
+
beforeSend(event, hint) {
|
|
184
|
+
// Strip PII from error context
|
|
185
|
+
if (event.user) {
|
|
186
|
+
delete event.user.email;
|
|
187
|
+
delete event.user.username;
|
|
188
|
+
}
|
|
189
|
+
return event;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
integrations: [
|
|
193
|
+
Sentry.replayIntegration({
|
|
194
|
+
maskAllText: true, // Privacy: mask all text in replays
|
|
195
|
+
blockAllMedia: true, // Privacy: block media in replays
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### CDN Analytics and Cache Hit Rate
|
|
202
|
+
|
|
203
|
+
Monitor CDN cache performance as a leading indicator for LCP and TTFB:
|
|
204
|
+
|
|
205
|
+
- **Cache hit rate** — percentage of requests served from CDN cache vs origin. Target >90% for static assets, >70% for edge-cached HTML.
|
|
206
|
+
- **Origin shield hit rate** — for CDN tiers that include an origin shield, a low rate indicates cold cache or poor cache-control headers.
|
|
207
|
+
- **Edge latency by region** — identify geographic regions where users have poor performance; expand CDN PoP coverage or investigate origin latency.
|
|
208
|
+
|
|
209
|
+
Key cache-control headers:
|
|
210
|
+
```
|
|
211
|
+
# Immutable static assets (hashed filenames)
|
|
212
|
+
Cache-Control: public, max-age=31536000, immutable
|
|
213
|
+
|
|
214
|
+
# HTML pages — revalidate frequently but serve stale while revalidating
|
|
215
|
+
Cache-Control: public, max-age=0, s-maxage=86400, stale-while-revalidate=3600
|
|
216
|
+
|
|
217
|
+
# API responses — no public cache, short browser cache
|
|
218
|
+
Cache-Control: private, max-age=30
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Set `Surrogate-Key` (Fastly) or `Cache-Tag` (Cloudflare) headers on HTML responses to enable targeted cache purging when content changes.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-project-structure
|
|
3
|
+
description: Directory layout conventions, route organization, shared vs feature modules, barrel exports, and config file placement for web apps
|
|
4
|
+
topics: [web-app, project-structure, architecture, routing, modules]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A well-structured web app project is navigable by any developer without a tour. The directory layout should communicate the architecture at a glance: where pages live, where shared code lives, where API logic lives, and where configuration lives. A poor structure forces every developer to ask "where does this go?" on every new file they create.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Web app project structure separates `app/` (routes and layouts), `features/` (domain-owned modules), `components/` (shared UI), `hooks/` (shared hooks), and `lib/` (framework-agnostic utilities). Routes mirror URL structure with thin page files. Feature modules own their components, hooks, and types; shared modules have zero imports from feature modules.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Standard Directory Layout
|
|
16
|
+
|
|
17
|
+
For a Next.js or similar SSR framework, the canonical structure is:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
app/ # App Router pages and layouts (Next.js 13+)
|
|
22
|
+
pages/ # Pages Router (Next.js legacy; or Remix/Vite routes)
|
|
23
|
+
components/ # Shared, reusable UI components
|
|
24
|
+
features/ # Feature modules (colocated components + hooks + logic)
|
|
25
|
+
hooks/ # Shared custom hooks used across features
|
|
26
|
+
lib/ # Framework-agnostic utilities and service clients
|
|
27
|
+
api/ # API route handlers (serverless functions)
|
|
28
|
+
types/ # Shared TypeScript types and interfaces
|
|
29
|
+
styles/ # Global CSS, design tokens, theme config
|
|
30
|
+
public/ # Static assets served at root (images, fonts, robots.txt)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For a Vite/React SPA, drop `app/` and `api/` and substitute:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
src/
|
|
37
|
+
routes/ # Route components and nested layouts
|
|
38
|
+
pages/ # Page-level components (one per route)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Route Organization
|
|
42
|
+
|
|
43
|
+
Routes should mirror the URL structure. Avoid flat route files that make the hierarchy ambiguous:
|
|
44
|
+
|
|
45
|
+
- `/dashboard` → `app/dashboard/page.tsx`
|
|
46
|
+
- `/dashboard/settings` → `app/dashboard/settings/page.tsx`
|
|
47
|
+
- `/users/[id]` → `app/users/[id]/page.tsx`
|
|
48
|
+
|
|
49
|
+
Keep route files thin — they orchestrate data loading and pass props to feature components. Business logic belongs in `features/`, not in the page file.
|
|
50
|
+
|
|
51
|
+
**Layouts**: Use layout files (`layout.tsx`) to share navigation, auth checks, and providers across route groups. Do not repeat these in every page file.
|
|
52
|
+
|
|
53
|
+
### Shared vs Feature Modules
|
|
54
|
+
|
|
55
|
+
The critical distinction is "who owns this code":
|
|
56
|
+
|
|
57
|
+
- **Feature module** (`features/checkout/`): Owned by one product domain. Contains everything the checkout feature needs — components, hooks, API calls, types. Other features do not import from it directly; they use shared interfaces.
|
|
58
|
+
- **Shared module** (`components/`, `hooks/`, `lib/`): Owned by no single feature. Used by two or more features. Has no knowledge of any specific feature's business logic.
|
|
59
|
+
|
|
60
|
+
The rule: shared modules must have zero imports from feature modules. Feature modules may import from shared modules. This prevents circular dependencies and keeps shared code reusable.
|
|
61
|
+
|
|
62
|
+
### Barrel Exports
|
|
63
|
+
|
|
64
|
+
Use `index.ts` barrel files at the feature and component directory level to create clean import paths:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// features/user-profile/index.ts
|
|
68
|
+
export { UserProfile } from "./UserProfile";
|
|
69
|
+
export { useUserProfile } from "./useUserProfile";
|
|
70
|
+
export type { UserProfileData } from "./user-profile.types";
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Consumers import from `features/user-profile`, not from deep internal paths:
|
|
74
|
+
```typescript
|
|
75
|
+
// Good
|
|
76
|
+
import { UserProfile } from "@/features/user-profile";
|
|
77
|
+
|
|
78
|
+
// Bad — leaks internal structure
|
|
79
|
+
import { UserProfile } from "@/features/user-profile/components/UserProfile";
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Configure TypeScript path aliases (`@/`) to avoid relative path ladders (`../../../../../../`).
|
|
83
|
+
|
|
84
|
+
### Config Files
|
|
85
|
+
|
|
86
|
+
Config files belong at the repo root, not inside `src/`. Standard placement:
|
|
87
|
+
|
|
88
|
+
- `next.config.ts` / `vite.config.ts` — build and bundler config
|
|
89
|
+
- `tsconfig.json` — TypeScript compiler config
|
|
90
|
+
- `eslint.config.js` — linting rules
|
|
91
|
+
- `.env.example` — documented environment variables (committed); `.env.local` — actual values (gitignored)
|
|
92
|
+
- `tailwind.config.ts` — if using Tailwind
|
|
93
|
+
- `vitest.config.ts` / `jest.config.ts` — test runner config
|
|
94
|
+
|
|
95
|
+
### Feature Module Template
|
|
96
|
+
|
|
97
|
+
Every feature module follows the same internal structure. Establish this template in `CONTRIBUTING.md`:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
features/
|
|
101
|
+
<feature-name>/
|
|
102
|
+
index.ts # Barrel export — public API of the feature
|
|
103
|
+
<FeatureName>.tsx # Top-level feature component
|
|
104
|
+
<FeatureName>.test.tsx # Component tests
|
|
105
|
+
<FeatureName>.stories.tsx # Storybook stories (if applicable)
|
|
106
|
+
use<FeatureName>.ts # Primary data hook
|
|
107
|
+
use<FeatureName>.test.ts # Hook tests
|
|
108
|
+
<feature-name>.types.ts # TypeScript types for this feature
|
|
109
|
+
<feature-name>.api.ts # API calls made by this feature (if applicable)
|
|
110
|
+
components/ # Sub-components used only within this feature
|
|
111
|
+
<SubComponent>.tsx
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Enforce "no cross-feature imports" with ESLint:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// eslint-plugin-import rule to prevent cross-feature imports
|
|
118
|
+
"import/no-restricted-paths": [
|
|
119
|
+
"error",
|
|
120
|
+
{
|
|
121
|
+
"zones": [{
|
|
122
|
+
"target": "./src/features",
|
|
123
|
+
"from": "./src/features",
|
|
124
|
+
"except": ["./src/features/index.ts"] // Only barrel is importable
|
|
125
|
+
}]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Environment Variable Management
|
|
131
|
+
|
|
132
|
+
Never hardcode environment-specific values. Use `.env` files and document every variable:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# .env.example — committed to the repo
|
|
136
|
+
# Never commit .env, .env.local, or .env.production
|
|
137
|
+
|
|
138
|
+
# API base URL
|
|
139
|
+
NEXT_PUBLIC_API_URL=https://api.example.com
|
|
140
|
+
|
|
141
|
+
# Auth provider
|
|
142
|
+
NEXT_PUBLIC_AUTH_DOMAIN=your-domain.auth0.com
|
|
143
|
+
AUTH_SECRET= # Server-only; never expose to client (no NEXT_PUBLIC_ prefix)
|
|
144
|
+
|
|
145
|
+
# Feature flags
|
|
146
|
+
NEXT_PUBLIC_FEATURE_NEW_CHECKOUT=false
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Validate environment variables at startup using a schema (Zod is ideal). Fail fast with a clear error message if required variables are missing or malformed. Never let the app silently run with bad configuration.
|
|
150
|
+
|
|
151
|
+
### Avoiding the "Utils Dumping Ground"
|
|
152
|
+
|
|
153
|
+
A `utils/` directory with no sub-organization becomes a junk drawer within weeks. Apply the same discipline to utilities:
|
|
154
|
+
|
|
155
|
+
- Group by domain: `lib/date.ts`, `lib/currency.ts`, `lib/validation.ts`
|
|
156
|
+
- If a utility grows beyond ~100 lines, give it its own directory with an `index.ts`
|
|
157
|
+
- Delete utilities that are no longer used — dead code in `lib/` is worse than no code
|
|
158
|
+
- Never put business logic in `lib/`. Business logic belongs in features or hooks.
|
|
159
|
+
|
|
160
|
+
The test for whether something belongs in `lib/`: "Could I copy this to a different web app project without modification?" If yes, it belongs in `lib/`. If it has knowledge of this app's domain, it belongs in `features/` or `hooks/`.
|