@zigrivers/scaffold 3.6.0 → 3.7.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.
Files changed (89) hide show
  1. package/README.md +87 -12
  2. package/content/knowledge/backend/backend-api-design.md +103 -0
  3. package/content/knowledge/backend/backend-architecture.md +100 -0
  4. package/content/knowledge/backend/backend-async-patterns.md +101 -0
  5. package/content/knowledge/backend/backend-auth-patterns.md +100 -0
  6. package/content/knowledge/backend/backend-conventions.md +105 -0
  7. package/content/knowledge/backend/backend-data-modeling.md +102 -0
  8. package/content/knowledge/backend/backend-deployment.md +100 -0
  9. package/content/knowledge/backend/backend-dev-environment.md +102 -0
  10. package/content/knowledge/backend/backend-observability.md +102 -0
  11. package/content/knowledge/backend/backend-project-structure.md +100 -0
  12. package/content/knowledge/backend/backend-requirements.md +103 -0
  13. package/content/knowledge/backend/backend-security.md +104 -0
  14. package/content/knowledge/backend/backend-testing.md +101 -0
  15. package/content/knowledge/backend/backend-worker-patterns.md +100 -0
  16. package/content/knowledge/cli/cli-architecture.md +101 -0
  17. package/content/knowledge/cli/cli-conventions.md +117 -0
  18. package/content/knowledge/cli/cli-dev-environment.md +121 -0
  19. package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
  20. package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
  21. package/content/knowledge/cli/cli-output-patterns.md +107 -0
  22. package/content/knowledge/cli/cli-project-structure.md +124 -0
  23. package/content/knowledge/cli/cli-requirements.md +101 -0
  24. package/content/knowledge/cli/cli-shell-integration.md +130 -0
  25. package/content/knowledge/cli/cli-testing.md +134 -0
  26. package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
  27. package/content/knowledge/web-app/web-app-architecture.md +116 -0
  28. package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
  29. package/content/knowledge/web-app/web-app-conventions.md +121 -0
  30. package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
  31. package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
  32. package/content/knowledge/web-app/web-app-deployment.md +134 -0
  33. package/content/knowledge/web-app/web-app-design-system.md +158 -0
  34. package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
  35. package/content/knowledge/web-app/web-app-observability.md +221 -0
  36. package/content/knowledge/web-app/web-app-project-structure.md +160 -0
  37. package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
  38. package/content/knowledge/web-app/web-app-requirements.md +112 -0
  39. package/content/knowledge/web-app/web-app-security.md +193 -0
  40. package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
  41. package/content/knowledge/web-app/web-app-testing.md +249 -0
  42. package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
  43. package/content/methodology/backend-overlay.yml +73 -0
  44. package/content/methodology/cli-overlay.yml +69 -0
  45. package/content/methodology/web-app-overlay.yml +79 -0
  46. package/dist/cli/commands/init.d.ts +12 -0
  47. package/dist/cli/commands/init.d.ts.map +1 -1
  48. package/dist/cli/commands/init.js +182 -13
  49. package/dist/cli/commands/init.js.map +1 -1
  50. package/dist/cli/commands/init.test.js +136 -0
  51. package/dist/cli/commands/init.test.js.map +1 -1
  52. package/dist/config/schema.d.ts +800 -32
  53. package/dist/config/schema.d.ts.map +1 -1
  54. package/dist/config/schema.js +48 -5
  55. package/dist/config/schema.js.map +1 -1
  56. package/dist/config/schema.test.js +156 -1
  57. package/dist/config/schema.test.js.map +1 -1
  58. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  59. package/dist/core/assembly/overlay-loader.js +2 -1
  60. package/dist/core/assembly/overlay-loader.js.map +1 -1
  61. package/dist/core/assembly/overlay-loader.test.js +34 -0
  62. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  63. package/dist/e2e/game-pipeline.test.js +1 -0
  64. package/dist/e2e/game-pipeline.test.js.map +1 -1
  65. package/dist/e2e/project-type-overlays.test.d.ts +15 -0
  66. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
  67. package/dist/e2e/project-type-overlays.test.js +534 -0
  68. package/dist/e2e/project-type-overlays.test.js.map +1 -0
  69. package/dist/types/config.d.ts +13 -2
  70. package/dist/types/config.d.ts.map +1 -1
  71. package/dist/types/index.d.ts +0 -1
  72. package/dist/types/index.d.ts.map +1 -1
  73. package/dist/types/index.js +0 -1
  74. package/dist/types/index.js.map +1 -1
  75. package/dist/wizard/questions.d.ts +16 -1
  76. package/dist/wizard/questions.d.ts.map +1 -1
  77. package/dist/wizard/questions.js +87 -3
  78. package/dist/wizard/questions.js.map +1 -1
  79. package/dist/wizard/questions.test.js +117 -4
  80. package/dist/wizard/questions.test.js.map +1 -1
  81. package/dist/wizard/wizard.d.ts +12 -0
  82. package/dist/wizard/wizard.d.ts.map +1 -1
  83. package/dist/wizard/wizard.js +16 -1
  84. package/dist/wizard/wizard.js.map +1 -1
  85. package/package.json +1 -1
  86. package/dist/types/wizard.d.ts +0 -14
  87. package/dist/types/wizard.d.ts.map +0 -1
  88. package/dist/types/wizard.js +0 -2
  89. package/dist/types/wizard.js.map +0 -1
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: cli-shell-integration
3
+ description: Shell completion generation for bash/zsh/fish, man page generation, dotfile conventions, PATH management, and shell aliases
4
+ topics: [cli, shell-integration, completion, man-pages, dotfiles, path-management, aliases]
5
+ ---
6
+
7
+ Shell integration is the difference between a CLI that feels native and one that feels like a foreign object. Completion, man pages, and dotfile patterns are not optional polish — they are the features that determine whether power users adopt the tool or abandon it for something that respects their shell workflow.
8
+
9
+ ## Summary
10
+
11
+ Shell integration — completion scripts, man pages, dotfile modifications, and PATH management — determines whether power users adopt the tool or abandon it. Generate completion scripts from the CLI definition for bash, zsh, and fish. Provide an `install-completions` subcommand. When modifying shell startup files, use clearly marked comment blocks and always provide an uninstall command.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Shell Completion Generation
16
+
17
+ Provide completion scripts for bash, zsh, and fish. Generate them from the CLI definition rather than hand-writing them — they will stay in sync as commands and flags evolve:
18
+
19
+ **Node.js (yargs)**
20
+ ```bash
21
+ my-cli completion # Outputs bash/zsh completion script
22
+ my-cli completion >> ~/.bashrc
23
+ ```
24
+
25
+ **Cobra (Go)**
26
+ ```bash
27
+ my-cli completion bash # bash
28
+ my-cli completion zsh # zsh
29
+ my-cli completion fish # fish
30
+ my-cli completion powershell
31
+ ```
32
+
33
+ **clap (Rust)** with `clap_complete`:
34
+ ```rust
35
+ // Generate at build time and install to /usr/local/share/bash-completion/completions/
36
+ generate(Shell::Bash, &mut cmd, "my-cli", &mut io::stdout());
37
+ ```
38
+
39
+ **click (Python)**: Use `click-completion` or the built-in `shell_complete` parameter.
40
+
41
+ Completion installation patterns:
42
+ - **bash**: Source from `~/.bashrc` or drop in `/etc/bash_completion.d/` or `~/.bash_completion.d/`
43
+ - **zsh**: Drop in `$fpath` directory, e.g., `/usr/local/share/zsh/site-functions/_my-cli`
44
+ - **fish**: Drop in `~/.config/fish/completions/my-cli.fish`
45
+
46
+ Provide a `my-cli install-completions` subcommand that writes the correct file for the detected shell. Always print the path written and what the user must do to activate it (e.g., restart shell or run `source ~/.bashrc`).
47
+
48
+ ### Man Page Generation
49
+
50
+ Man pages are the canonical reference documentation. Generate them from the CLI definition:
51
+
52
+ **Go (cobra)**: `cobra-man` generates man pages from Cobra commands.
53
+
54
+ **Node.js**: `marked-man` converts Markdown to man format. `ronn` (Ruby) converts richly formatted Markdown to man.
55
+
56
+ **Rust (clap)**: `clap_mangen` generates man pages from clap definitions.
57
+
58
+ Man page installation:
59
+ - System-wide: `/usr/local/share/man/man1/my-cli.1`
60
+ - User-local: `~/.local/share/man/man1/my-cli.1`
61
+ - Run `mandb` (Linux) or ensure `$MANPATH` includes the directory
62
+
63
+ Homebrew formulae automatically install man pages if placed in `share/man/man1/`. For npm packages, include a `man` field in `package.json`.
64
+
65
+ ### Dotfile Conventions
66
+
67
+ When the CLI modifies shell startup files (`~/.bashrc`, `~/.zshrc`), follow these rules:
68
+
69
+ - Never overwrite startup files — append only
70
+ - Use clearly marked comment blocks:
71
+ ```bash
72
+ # >>> my-cli init >>>
73
+ export PATH="$HOME/.my-cli/bin:$PATH"
74
+ eval "$(my-cli shell-init)"
75
+ # <<< my-cli init <<<
76
+ ```
77
+ - The markers enable the tool to detect existing installation and idempotently update the block
78
+ - Always provide `my-cli uninstall` or `my-cli shell-remove` that removes exactly these markers
79
+ - Never add content outside the marked block
80
+
81
+ On first install, detect which shell config files exist and are writable: check `$SHELL`, then look for `~/.zshrc`, `~/.bashrc`, `~/.bash_profile` in that order.
82
+
83
+ ### PATH Management
84
+
85
+ When the tool installs binaries or shims to a user-local directory:
86
+
87
+ ```bash
88
+ # Typical user-local bin directory
89
+ ~/.local/bin/ # XDG standard (Linux/macOS)
90
+ ~/.my-cli/bin/ # Tool-specific (when multiple versions coexist)
91
+ ```
92
+
93
+ Check if the directory is already on `$PATH` before suggesting the user add it. If not on `$PATH`, print the exact line to add and which file to add it to:
94
+
95
+ ```
96
+ Add this to ~/.zshrc:
97
+ export PATH="$HOME/.local/bin:$PATH"
98
+ ```
99
+
100
+ Do not silently modify PATH for the current process and assume it persists — shell environment changes only persist through startup file modification or explicit user action.
101
+
102
+ ### Shell Aliases
103
+
104
+ Provide a mechanism to generate useful shell aliases for common command combinations:
105
+
106
+ ```bash
107
+ # my-cli generates aliases for common workflows
108
+ my-cli alias generate >> ~/.zshrc
109
+ ```
110
+
111
+ Aliases should be documented and user-editable. Never require aliases for core functionality — they are convenience, not architecture.
112
+
113
+ ### Shell Detection
114
+
115
+ Detect the user's shell reliably:
116
+
117
+ ```bash
118
+ # Most reliable: check $SHELL environment variable
119
+ SHELL_NAME=$(basename "$SHELL")
120
+
121
+ # Fallback: check running process
122
+ ps -p $PPID -o comm= | sed 's/^-//'
123
+ ```
124
+
125
+ Map shell name to config file:
126
+ - `zsh` → `~/.zshrc`
127
+ - `bash` → `~/.bashrc` (Linux) or `~/.bash_profile` (macOS interactive login shells)
128
+ - `fish` → `~/.config/fish/config.fish`
129
+
130
+ When the shell cannot be detected, print instructions for all three common shells and let the user pick. Never guess and silently write to the wrong file.
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: cli-testing
3
+ description: CLI integration testing by spawning processes, snapshot testing help text, mock filesystem, environment variable testing, and CI matrix testing
4
+ topics: [cli, testing, integration-tests, snapshot-testing, mock-filesystem, ci-matrix, exit-codes]
5
+ ---
6
+
7
+ CLI testing requires a different mindset than library testing. The contract being tested is behavioral: given this argv and environment, what does the tool write to stdout, what does it write to stderr, and what exit code does it return? Unit tests for business logic are necessary but not sufficient — integration tests that spawn the actual binary catch the class of bugs that only appear at the boundary between argument parsing and execution.
8
+
9
+ ## Summary
10
+
11
+ CLI testing requires spawning the actual binary and asserting on stdout, stderr, and exit code. Snapshot test help text to catch accidental regressions. Isolate filesystem tests with temporary directories and mock `$HOME`/`XDG_CONFIG_HOME`. Test across OS/runtime matrices in CI including Windows.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Integration Testing by Spawning the Process
16
+
17
+ The most valuable CLI test spawns the actual binary and asserts on stdout, stderr, and exit code:
18
+
19
+ **Node.js (vitest or jest)**
20
+ ```typescript
21
+ import { execSync } from 'child_process';
22
+
23
+ test('build succeeds with valid input', () => {
24
+ const result = execSync('node bin/my-cli build --input fixture.txt', {
25
+ encoding: 'utf8',
26
+ env: { ...process.env, XDG_CONFIG_HOME: tmpDir }
27
+ });
28
+ expect(result).toContain('Build complete');
29
+ });
30
+
31
+ test('exits 2 on unknown flag', () => {
32
+ expect(() =>
33
+ execSync('node bin/my-cli --unknown-flag', { encoding: 'utf8', stdio: 'pipe' })
34
+ ).toThrow('exit code 2');
35
+ });
36
+ ```
37
+
38
+ **Rust**
39
+ ```rust
40
+ use assert_cmd::Command;
41
+
42
+ #[test]
43
+ fn build_succeeds() {
44
+ Command::cargo_bin("my-cli")
45
+ .unwrap()
46
+ .arg("build")
47
+ .assert()
48
+ .success()
49
+ .stdout(predicates::str::contains("Build complete"));
50
+ }
51
+ ```
52
+
53
+ **Bats (shell)**
54
+ ```bash
55
+ @test "exits 2 on missing required argument" {
56
+ run my-cli deploy
57
+ [ "$status" -eq 2 ]
58
+ [[ "$output" =~ "required" ]]
59
+ }
60
+ ```
61
+
62
+ Always test exit codes. Always test that error output goes to stderr. Always test the success path and the most common failure paths.
63
+
64
+ ### Snapshot Testing for Help Text
65
+
66
+ Help text is part of the public API — changes should be intentional. Snapshot tests catch accidental regressions:
67
+
68
+ ```typescript
69
+ test('help text matches snapshot', () => {
70
+ const { stdout } = execSync('node bin/my-cli --help', { encoding: 'utf8' });
71
+ expect(stdout).toMatchSnapshot();
72
+ });
73
+ ```
74
+
75
+ Update snapshots intentionally when help text changes. In CI, fail on unexpected snapshot drift. This also catches typos and formatting issues in help output.
76
+
77
+ ### Mock Filesystem
78
+
79
+ Tests that interact with the filesystem must be isolated. Use temporary directories or a mock filesystem:
80
+
81
+ **Node.js**
82
+ ```typescript
83
+ import { mkdtempSync, rmSync } from 'fs';
84
+ import { tmpdir } from 'os';
85
+
86
+ let tmpDir: string;
87
+ beforeEach(() => { tmpDir = mkdtempSync(tmpdir() + '/my-cli-test-'); });
88
+ afterEach(() => { rmSync(tmpDir, { recursive: true }); });
89
+ ```
90
+
91
+ For unit tests that don't need real disk I/O, `memfs` provides an in-memory filesystem that satisfies the Node.js `fs` module interface.
92
+
93
+ **Rust**: Use `tempfile::TempDir`. Tests run in parallel by default — each test gets its own temp directory to avoid interference.
94
+
95
+ **Key rule**: Never write to `$HOME`, `~/.config`, or any real user directory in tests. Set `HOME` and `XDG_CONFIG_HOME` to the temp directory.
96
+
97
+ ### Environment Variable Testing
98
+
99
+ Test behavior driven by environment variables:
100
+
101
+ ```typescript
102
+ test('respects NO_COLOR', () => {
103
+ const result = execSync('node bin/my-cli status', {
104
+ encoding: 'utf8',
105
+ env: { ...process.env, NO_COLOR: '1' }
106
+ });
107
+ // Assert no ANSI escape sequences in output
108
+ expect(result).not.toMatch(/\x1b\[/);
109
+ });
110
+ ```
111
+
112
+ Test the full env var precedence chain: flag overrides env var, env var overrides config file. Each level should be independently testable.
113
+
114
+ ### CI Matrix Testing
115
+
116
+ Test across multiple operating systems and runtime versions:
117
+
118
+ ```yaml
119
+ # GitHub Actions
120
+ strategy:
121
+ matrix:
122
+ os: [ubuntu-latest, macos-latest, windows-latest]
123
+ node: ['18', '20', '22']
124
+ ```
125
+
126
+ Windows-specific concerns: path separators (`\` vs `/`), line endings (`\r\n` vs `\n`), `%APPDATA%` vs `$HOME/.config`, and `PATHEXT` for binary extension handling. Test on Windows even if you do not primarily develop on it.
127
+
128
+ ### Test Pyramid for CLIs
129
+
130
+ - **Unit tests (fast)**: Business logic in `utils/`, pure functions, config parsing, output formatters
131
+ - **Integration tests (medium)**: Spawn the CLI process, assert stdout/stderr/exit code for key scenarios
132
+ - **End-to-end tests (slow, optional)**: Full workflow against real external services — run in CI on schedule, not on every PR
133
+
134
+ Keep integration tests fast by using fixtures (pre-created files) rather than generating test input dynamically. A full integration test suite should complete in under 30 seconds.
@@ -0,0 +1,224 @@
1
+ ---
2
+ name: web-app-api-patterns
3
+ description: REST API design for web clients, GraphQL client patterns, error handling strategies, request deduplication, auth injection, and CORS
4
+ topics: [web-app, api, rest, graphql, cors, error-handling, auth]
5
+ ---
6
+
7
+ The API layer is the seam between frontend and backend. Poor design here manifests as waterfall requests that serialize page loads, inconsistent error shapes that require fragile client-side guessing, auth token handling bugs that cause random 401 errors, and CORS misconfigurations that block legitimate requests. A well-designed API client is boring: it handles auth transparently, errors consistently, and requests efficiently — so product engineers can focus on features rather than network plumbing.
8
+
9
+ ## Summary
10
+
11
+ ### REST API Design for Web Clients
12
+
13
+ REST conventions for web clients go beyond HTTP verb selection:
14
+
15
+ **Resource naming:** `/users/{id}/posts` not `/getUserPosts`. Nouns, not verbs. Plural collections.
16
+
17
+ **Consistent response envelope:** Every response should have a predictable shape. Either always return the resource directly (`200 OK` with the object) or always wrap it (`{ data: {...}, meta: {...} }`) — never mix.
18
+
19
+ **Status codes must be semantically correct:**
20
+ - `200 OK` — successful read
21
+ - `201 Created` — successful create (include `Location` header with new resource URL)
22
+ - `204 No Content` — successful delete or update with no response body
23
+ - `400 Bad Request` — validation failure (include field-level errors)
24
+ - `401 Unauthorized` — not authenticated
25
+ - `403 Forbidden` — authenticated but not authorized
26
+ - `404 Not Found` — resource does not exist
27
+ - `409 Conflict` — state conflict (duplicate resource, version mismatch)
28
+ - `422 Unprocessable Entity` — business rule violation
29
+ - `429 Too Many Requests` — rate limited (include `Retry-After` header)
30
+
31
+ **Error response shape (standardize on RFC 7807 Problem Details):**
32
+ ```json
33
+ {
34
+ "type": "https://api.example.com/errors/validation",
35
+ "title": "Validation Failed",
36
+ "status": 400,
37
+ "detail": "2 fields failed validation",
38
+ "errors": [
39
+ { "field": "email", "message": "Invalid email format" },
40
+ { "field": "username", "message": "Username already taken" }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ ### GraphQL Client Patterns
46
+
47
+ GraphQL shifts the API design from server-defined endpoints to client-defined data requirements:
48
+
49
+ **Fragments for co-location:** Define data requirements alongside the component that uses the data. This prevents over-fetching and makes it obvious when a component's data requirements change.
50
+
51
+ **Normalized caching:** Apollo Client and urql maintain a normalized cache where each entity is stored once (by `__typename + id`) and automatically updated across all queries that reference it. Mutations that return the updated entity automatically update all queries that include that entity.
52
+
53
+ **Fragment colocation pattern:** Each component defines its own fragment; the parent query composes them. This is the Relay-inspired pattern that scales to large teams.
54
+
55
+ ### Error Handling: Toast vs Inline
56
+
57
+ Two display patterns for API errors, each appropriate in different contexts:
58
+
59
+ **Toast notifications:** Background mutations, non-blocking operations, and errors where the user's current context is unchanged. "Failed to save changes — try again." The user doesn't lose their work.
60
+
61
+ **Inline errors:** Form submissions, operations where the error requires user action to resolve. Show the error adjacent to the field or action that caused it. A `400` validation error must show which fields failed.
62
+
63
+ **Never show raw API errors to users.** Map error codes to human-readable messages on the client. Log the technical detail to your error tracker.
64
+
65
+ ### Request Deduplication
66
+
67
+ Multiple components mounting simultaneously and each calling the same endpoint produces redundant parallel requests. Deduplication ensures the second identical in-flight request waits for the first's result rather than issuing a new request.
68
+
69
+ React Query deduplicates automatically — all components with the same `queryKey` share one in-flight request. For custom fetch layers, implement deduplication with a request-in-flight map.
70
+
71
+ ## Deep Guidance
72
+
73
+ ### API Client with Auth Injection and Token Refresh
74
+
75
+ ```typescript
76
+ // api-client.ts — Centralized API client with automatic token refresh
77
+ import ky from 'ky'; // Or axios, or native fetch
78
+
79
+ let isRefreshing = false;
80
+ let refreshQueue: Array<(token: string) => void> = [];
81
+
82
+ export const apiClient = ky.create({
83
+ prefixUrl: process.env.NEXT_PUBLIC_API_URL,
84
+ hooks: {
85
+ beforeRequest: [
86
+ async (request) => {
87
+ const token = getAccessToken(); // From memory, not localStorage
88
+ if (token) {
89
+ request.headers.set('Authorization', `Bearer ${token}`);
90
+ }
91
+ },
92
+ ],
93
+ afterResponse: [
94
+ async (request, options, response) => {
95
+ if (response.status !== 401) return response;
96
+
97
+ // Token expired — refresh and retry
98
+ if (isRefreshing) {
99
+ // Queue this request until refresh completes
100
+ return new Promise((resolve) => {
101
+ refreshQueue.push((newToken) => {
102
+ request.headers.set('Authorization', `Bearer ${newToken}`);
103
+ resolve(ky(request));
104
+ });
105
+ });
106
+ }
107
+
108
+ isRefreshing = true;
109
+ try {
110
+ const newToken = await refreshAccessToken();
111
+ setAccessToken(newToken);
112
+
113
+ // Replay all queued requests with new token
114
+ refreshQueue.forEach(cb => cb(newToken));
115
+ refreshQueue = [];
116
+
117
+ // Retry the original request
118
+ request.headers.set('Authorization', `Bearer ${newToken}`);
119
+ return ky(request);
120
+ } catch (refreshError) {
121
+ // Refresh failed — redirect to login
122
+ clearSession();
123
+ window.location.href = '/login';
124
+ throw refreshError;
125
+ } finally {
126
+ isRefreshing = false;
127
+ }
128
+ },
129
+ ],
130
+ },
131
+ });
132
+ ```
133
+
134
+ The token refresh queue pattern prevents multiple simultaneous 401s from triggering multiple refresh requests. Only the first request initiates the refresh; subsequent requests queue and replay once the token is available.
135
+
136
+ ### CORS Configuration
137
+
138
+ CORS is a browser-enforced security mechanism, not an API security mechanism. Servers must explicitly allow cross-origin requests from trusted origins.
139
+
140
+ ```typescript
141
+ // Express CORS configuration
142
+ import cors from 'cors';
143
+
144
+ const allowedOrigins = [
145
+ 'https://app.example.com',
146
+ 'https://www.example.com',
147
+ // Development origins — only in non-production
148
+ ...(process.env.NODE_ENV !== 'production' ? ['http://localhost:3000'] : []),
149
+ ];
150
+
151
+ app.use(cors({
152
+ origin: (origin, callback) => {
153
+ // Allow requests with no origin (server-to-server, curl)
154
+ if (!origin) return callback(null, true);
155
+ if (allowedOrigins.includes(origin)) return callback(null, true);
156
+ callback(new Error(`Origin ${origin} not allowed by CORS policy`));
157
+ },
158
+ credentials: true, // Required when client sends cookies
159
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
160
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
161
+ exposedHeaders: ['X-Request-ID', 'X-Rate-Limit-Remaining'],
162
+ maxAge: 86400, // Cache preflight for 24 hours (reduces OPTIONS requests)
163
+ }));
164
+ ```
165
+
166
+ **CORS mistakes to avoid:**
167
+ - `origin: '*'` with `credentials: true` is invalid and rejected by browsers
168
+ - Wildcard subdomain matching (`*.example.com`) requires explicit pattern matching, not a string
169
+ - Missing `maxAge` causes an OPTIONS preflight on every non-simple cross-origin request
170
+
171
+ ### GraphQL Fragment Colocation
172
+
173
+ ```typescript
174
+ // UserCard.tsx — Component defines its own data requirements
175
+ const USER_CARD_FRAGMENT = gql`
176
+ fragment UserCardFields on User {
177
+ id
178
+ displayName
179
+ avatarUrl
180
+ followerCount
181
+ }
182
+ `;
183
+
184
+ function UserCard({ user }: { user: UserCardFields }) {
185
+ return (/* render using user.displayName, user.avatarUrl, etc. */);
186
+ }
187
+
188
+ UserCard.fragments = { user: USER_CARD_FRAGMENT };
189
+
190
+ // ProfilePage.tsx — Composes fragments, doesn't know what UserCard needs
191
+ const PROFILE_PAGE_QUERY = gql`
192
+ query ProfilePage($userId: ID!) {
193
+ user(id: $userId) {
194
+ ...UserCardFields
195
+ email # Only ProfilePage needs this
196
+ createdAt
197
+ }
198
+ }
199
+ ${UserCard.fragments.user}
200
+ `;
201
+ ```
202
+
203
+ When `UserCard` needs a new field, the change is isolated to the fragment and its parent query is automatically updated. No need to modify multiple components or add fields "just in case."
204
+
205
+ ### API Error Handling Pattern
206
+
207
+ ```typescript
208
+ // Typed error handling — map API errors to user-facing messages
209
+ const API_ERROR_MESSAGES: Record<string, string> = {
210
+ 'validation/email-taken': 'That email address is already registered.',
211
+ 'auth/session-expired': 'Your session has expired. Please sign in again.',
212
+ 'rate-limit/exceeded': 'Too many attempts. Please wait a moment and try again.',
213
+ };
214
+
215
+ function getErrorMessage(error: ApiError): string {
216
+ if (error.type && API_ERROR_MESSAGES[error.type]) {
217
+ return API_ERROR_MESSAGES[error.type];
218
+ }
219
+ // Safe fallback — never expose raw server errors
220
+ return 'Something went wrong. Please try again.';
221
+ }
222
+ ```
223
+
224
+ Maintain error message strings in one place. Never construct user-facing messages from raw `error.message` strings returned by the server.
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: web-app-architecture
3
+ description: Rendering strategy tradeoffs, CDN edge patterns, hydration strategies, BFF pattern, and micro-frontend considerations for web apps
4
+ topics: [web-app, architecture, ssr, ssg, spa, hydration, bff, micro-frontends, cdn]
5
+ ---
6
+
7
+ Web application architecture is the set of decisions that are expensive to reverse: rendering strategy, client-server boundary, data fetching patterns, and infrastructure topology. These decisions must be made with explicit tradeoff acknowledgment and documented as Architecture Decision Records. The most common architectural mistake is choosing a sophisticated pattern because it is interesting, not because the problem demands it.
8
+
9
+ ## Summary
10
+
11
+ Web app architecture decisions — rendering strategy (CSR, SSG, SSR, ISR, hybrid), CDN edge patterns, hydration strategy, and the BFF pattern — are expensive to reverse. Match each strategy to the use case rather than trends. Most real-world apps benefit from a hybrid approach mixing strategies per route. Document every significant architectural choice as an Architecture Decision Record.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Rendering Strategy Tradeoffs
16
+
17
+ Every rendering strategy has a cost and a benefit profile. Match the strategy to the use case:
18
+
19
+ **CSR (Client-Side Rendering)**
20
+ - All rendering happens in the browser; server delivers a shell HTML + JS bundle
21
+ - Benefits: Simplest server infrastructure (static file hosting), excellent for authenticated apps with no SEO requirement
22
+ - Costs: Poor initial LCP (blank page until JS loads), crawlers may not index content, no server-side data access
23
+ - Use when: Dashboard, admin panel, authenticated tools, single-page apps behind login
24
+
25
+ **SSG (Static Site Generation)**
26
+ - Pages built at deploy time; served as static files from CDN
27
+ - Benefits: Near-zero TTFB, cheapest to serve, CDN-native, best Lighthouse scores
28
+ - Costs: Data is stale until next deploy, build time grows with page count (thousands of pages = slow builds)
29
+ - Use when: Marketing sites, documentation, blogs, any content that changes infrequently
30
+
31
+ **SSR (Server-Side Rendering)**
32
+ - Every request rendered on the server; fresh data every time
33
+ - Benefits: SEO-friendly, always fresh data, works without JavaScript enabled, good LCP on slow connections
34
+ - Costs: Server infrastructure required, cold start latency, harder caching, session management complexity
35
+ - Use when: E-commerce product pages, news sites, any dynamic content that must be SEO-indexed
36
+
37
+ **ISR (Incremental Static Regeneration)**
38
+ - Static generation with time-based revalidation; stale-while-revalidate per route
39
+ - Benefits: CDN-served like SSG, data freshness configurable per route (10 seconds to 24 hours)
40
+ - Costs: Staleness window must be acceptable to the business, first-visitor after expiry pays SSR cost
41
+ - Use when: Product listings, blog index pages, any content where "minutes stale" is acceptable
42
+
43
+ **Hybrid**
44
+ - Mix strategies per route: SSG for marketing pages, SSR for product pages, CSR for dashboard
45
+ - Next.js and Remix enable hybrid out of the box — this is the recommended approach for most real-world apps
46
+
47
+ ### CDN Edge Patterns
48
+
49
+ Deploy static assets and, when possible, entire page renders to CDN edge nodes geographically close to users:
50
+
51
+ - **Edge caching**: Cache SSR responses at the CDN by setting appropriate `Cache-Control` headers. A page with `Cache-Control: s-maxage=60` is served from CDN for 60 seconds before revalidation.
52
+ - **Edge middleware**: Run logic at the CDN edge (Cloudflare Workers, Vercel Edge Functions) for auth redirects, A/B testing, and geolocation routing without hitting the origin server.
53
+ - **CDN-first asset serving**: All static assets (JS bundles, CSS, images) should always be CDN-served with long-lived cache headers (`Cache-Control: immutable, max-age=31536000`) and content-hashed filenames. Frameworks handle this automatically at build time.
54
+
55
+ ### Hydration Strategies
56
+
57
+ Hydration is the process of attaching JavaScript interactivity to server-rendered HTML. It is the primary performance cost of SSR frameworks:
58
+
59
+ - **Full hydration**: The entire component tree hydrates at load. Simplest, but pays full JS parse/execute cost even for static content. Next.js and Remix default behavior.
60
+ - **Progressive hydration**: Hydrate high-priority interactive components first (navigation, above-fold CTAs), defer lower-priority components. Reduces TTI (Time to Interactive) without reducing interactivity.
61
+ - **Selective hydration / Islands architecture**: Only interactive components hydrate; purely static content never runs JS. Implemented by Astro natively; Qwik takes this further with resumability.
62
+ - **React Server Components (RSC)**: Components marked `"server"` never ship to the client. Their rendered output is streamed as a serialized component tree, not HTML. Zero hydration cost for server components.
63
+
64
+ Match hydration strategy to interactivity requirements. A content site with sparse interactive elements benefits significantly from islands architecture. A complex dashboard with no static content benefits from full hydration.
65
+
66
+ ### BFF (Backend for Frontend) Pattern
67
+
68
+ The BFF pattern places a purpose-built server layer between the frontend and backend microservices:
69
+
70
+ - **Problem solved**: Frontend needs data aggregated from 5 microservices to render one page. Directly calling all 5 from the client creates waterfall requests, exposes internal service URLs, and splits auth concerns.
71
+ - **BFF solution**: The frontend calls one BFF endpoint. The BFF aggregates service responses, transforms data to match the UI's needs, handles auth tokens, and returns a single optimized response.
72
+ - **Implementation**: Next.js API routes or Remix loader functions are natural BFF layers. For separate backend services: a Node.js/Express/Fastify service that the frontend treats as its own API.
73
+
74
+ Do not build a BFF that becomes an unowned monolith. The BFF is owned by the frontend team and changes with the frontend's needs.
75
+
76
+ ### Architecture Decision Record Template
77
+
78
+ Document every significant architectural choice:
79
+
80
+ ```markdown
81
+ # ADR-001: Rendering Strategy Selection
82
+
83
+ ## Status
84
+ Accepted — 2024-01-15
85
+
86
+ ## Context
87
+ Marketing site (40 pages, infrequent content updates) plus authenticated dashboard.
88
+
89
+ ## Decision
90
+ - Marketing pages: SSG via Next.js; rebuild on content change via webhook
91
+ - Dashboard: CSR with React Query; no SEO requirement
92
+
93
+ ## Consequences
94
+ - Marketing pages: near-zero TTFB, excellent SEO, requires rebuild pipeline
95
+ - Dashboard: full JS bundle on load; auth gate prevents SEO concerns
96
+ - Hybrid in one codebase (Next.js handles both routing strategies)
97
+ ```
98
+
99
+ ### Micro-Frontend Considerations
100
+
101
+ Micro-frontends split one frontend monolith into independently deployable pieces, each owned by a different team. Adopt only when the organizational cost exceeds the technical cost:
102
+
103
+ - **When justified**: 50+ frontend engineers across 10+ teams, independent release cadences are blocked by coordination overhead, different teams own completely separate product domains
104
+ - **When NOT justified**: Under 20 engineers, single team owns the frontend, coordination overhead is manageable
105
+ - **Implementation options**: Module Federation (webpack), iframe composition (isolation but poor UX), server-side composition (ESI, Nginx include), link-based navigation (least coupling, least shared state)
106
+
107
+ Most teams that adopt micro-frontends at 10 engineers regret it. Prefer well-structured monorepos with internal package boundaries first.
108
+
109
+ ### Streaming SSR
110
+
111
+ React 18+ enables streaming HTML responses: send the page shell immediately, stream in component subtrees as their data resolves. This dramatically improves TTFB perception and LCP:
112
+
113
+ - Critical path renders immediately; slow data sources do not block the initial HTML flush
114
+ - Implement with React's `<Suspense>` boundaries and `renderToPipeableStream`
115
+ - Next.js App Router uses streaming by default; wrap slow data-fetching components in `<Suspense>`
116
+ - Monitor streaming behavior in production — if a slow database query is not wrapped in Suspense, it blocks the entire response stream