opencastle 0.9.0 → 0.9.1
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/package.json +1 -1
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +143 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +125 -12
- package/src/orchestrator/skills/frontend-design/SKILL.md +152 -1
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +147 -24
- package/src/orchestrator/skills/seo-patterns/SKILL.md +182 -24
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
2
|
+
"hash": "d5604882",
|
|
3
3
|
"configHash": "30f8ea04",
|
|
4
|
-
"lockfileHash": "
|
|
5
|
-
"browserHash": "
|
|
4
|
+
"lockfileHash": "f8cf45f5",
|
|
5
|
+
"browserHash": "6444ef19",
|
|
6
6
|
"optimized": {
|
|
7
7
|
"astro > cssesc": {
|
|
8
8
|
"src": "../../../../../node_modules/cssesc/cssesc.js",
|
|
9
9
|
"file": "astro___cssesc.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "3cc60f43",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"astro > aria-query": {
|
|
14
14
|
"src": "../../../../../node_modules/aria-query/lib/index.js",
|
|
15
15
|
"file": "astro___aria-query.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "173d601a",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"astro > axobject-query": {
|
|
20
20
|
"src": "../../../../../node_modules/axobject-query/lib/index.js",
|
|
21
21
|
"file": "astro___axobject-query.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "330d8b1f",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
}
|
|
25
25
|
},
|
|
@@ -19,6 +19,119 @@ All deployment configuration is project-specific. See [deployment-config.md](../
|
|
|
19
19
|
- Use short cache durations for frequently changing assets (e.g., favicon: `max-age=86400`)
|
|
20
20
|
- Load the **security-hardening** skill for full header inventory and CSP configuration
|
|
21
21
|
|
|
22
|
+
## Environment Variable Management
|
|
23
|
+
|
|
24
|
+
### Layering & Precedence
|
|
25
|
+
|
|
26
|
+
Environment variables follow a layered override model (lowest to highest priority):
|
|
27
|
+
|
|
28
|
+
1. `.env` — shared defaults, committed to repo (no secrets)
|
|
29
|
+
2. `.env.local` — developer-specific overrides, git-ignored
|
|
30
|
+
3. `.env.production` / `.env.preview` — environment-specific values
|
|
31
|
+
4. Platform-injected variables — set in hosting dashboard, highest priority
|
|
32
|
+
|
|
33
|
+
### Validation at Startup
|
|
34
|
+
|
|
35
|
+
Validate required variables at application startup. Fail fast with clear messages:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// src/lib/env.ts
|
|
39
|
+
import { z } from 'zod';
|
|
40
|
+
|
|
41
|
+
const envSchema = z.object({
|
|
42
|
+
DATABASE_URL: z.string().url(),
|
|
43
|
+
API_SECRET: z.string().min(32),
|
|
44
|
+
PUBLIC_SITE_URL: z.string().url(),
|
|
45
|
+
CRON_SECRET: z.string().min(16),
|
|
46
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const env = envSchema.parse(process.env);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Naming & .gitignore
|
|
53
|
+
|
|
54
|
+
- `PUBLIC_*` or `NEXT_PUBLIC_*` — safe to expose to the browser
|
|
55
|
+
- `SECRET_*` or `*_SECRET` — server-only, never bundled into client code
|
|
56
|
+
- `CRON_SECRET` — authenticates scheduled job endpoints
|
|
57
|
+
- Use `SCREAMING_SNAKE_CASE` for all variable names
|
|
58
|
+
- Always gitignore `.env.local`, `.env.*.local`, and `.env.production`
|
|
59
|
+
|
|
60
|
+
## CI/CD Pipeline Patterns
|
|
61
|
+
|
|
62
|
+
### Branch-Based Deployment
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
main branch → Production deployment (auto)
|
|
66
|
+
feature/* → Preview deployment (auto)
|
|
67
|
+
fix/* → Preview deployment (auto)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Generic Pipeline Stages
|
|
71
|
+
|
|
72
|
+
Every pipeline should include these stages in order:
|
|
73
|
+
|
|
74
|
+
1. **Install** — restore dependencies from lockfile (`--frozen-lockfile`)
|
|
75
|
+
2. **Lint** — static analysis and formatting checks
|
|
76
|
+
3. **Test** — unit and integration tests with coverage
|
|
77
|
+
4. **Build** — production build (catches type errors and build-time issues)
|
|
78
|
+
5. **Deploy** — push artifacts to hosting platform
|
|
79
|
+
|
|
80
|
+
### Cron Job Authentication
|
|
81
|
+
|
|
82
|
+
Protect scheduled endpoints with a shared secret:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// app/api/cron/route.ts
|
|
86
|
+
export async function GET(request: Request) {
|
|
87
|
+
const authHeader = request.headers.get('authorization');
|
|
88
|
+
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
89
|
+
return new Response('Unauthorized', { status: 401 });
|
|
90
|
+
}
|
|
91
|
+
// ... run scheduled task
|
|
92
|
+
return Response.json({ ok: true });
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Caching Strategy
|
|
97
|
+
|
|
98
|
+
### Cache Duration Reference
|
|
99
|
+
|
|
100
|
+
| Asset Type | `Cache-Control` Header | Rationale |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| Hashed static assets (JS, CSS) | `public, max-age=31536000, immutable` | Content-addressed filenames; safe to cache forever |
|
|
103
|
+
| Images / fonts | `public, max-age=31536000, immutable` | Typically fingerprinted; long-lived |
|
|
104
|
+
| Favicon / manifest | `public, max-age=86400` | Changes rarely but should refresh within a day |
|
|
105
|
+
| HTML pages (SSG) | `public, max-age=0, must-revalidate` | Serve stale while revalidating |
|
|
106
|
+
| API responses | `private, no-cache` | User-specific or frequently changing |
|
|
107
|
+
| Prerendered pages (ISR) | `public, s-maxage=3600, stale-while-revalidate=86400` | CDN caches for 1 hour, serves stale for up to 1 day |
|
|
108
|
+
|
|
109
|
+
Apply cache headers via framework config (e.g., `headers()` in `next.config.js`) or CDN rules. Match each route pattern to the appropriate duration from the table above.
|
|
110
|
+
|
|
111
|
+
## Security Headers
|
|
112
|
+
|
|
113
|
+
Apply these headers globally via framework config or middleware. See the **security-hardening** skill for full CSP configuration.
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
// Recommended security headers
|
|
117
|
+
const securityHeaders = [
|
|
118
|
+
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
|
|
119
|
+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
120
|
+
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
121
|
+
{ key: 'X-XSS-Protection', value: '1; mode=block' },
|
|
122
|
+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
123
|
+
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
|
|
124
|
+
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-inline';" },
|
|
125
|
+
];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Key rules:**
|
|
129
|
+
|
|
130
|
+
- HSTS `max-age` must be at least 1 year (31536000 seconds) for preload eligibility
|
|
131
|
+
- `X-Frame-Options: DENY` prevents clickjacking — use `SAMEORIGIN` only if you embed your own pages
|
|
132
|
+
- CSP should be as restrictive as possible; expand only when needed, document each exception
|
|
133
|
+
- Disable unused browser features via `Permissions-Policy`
|
|
134
|
+
|
|
22
135
|
## Release Process
|
|
23
136
|
|
|
24
137
|
### 1. Pre-Release Audit
|
|
@@ -31,7 +144,7 @@ All deployment configuration is project-specific. See [deployment-config.md](../
|
|
|
31
144
|
- Identify features adjacent to changes and spot-check them
|
|
32
145
|
- Run full test suites for all affected projects (not just changed files)
|
|
33
146
|
- Check deployment preview builds for visual regressions
|
|
34
|
-
- Verify critical user flows still work (
|
|
147
|
+
- Verify critical user flows still work (e.g., primary navigation, form submissions, authenticated pages)
|
|
35
148
|
|
|
36
149
|
### 3. Changelog & Release Notes
|
|
37
150
|
- Generate changelog from commit messages and PR titles since last release
|
|
@@ -48,4 +161,32 @@ All deployment configuration is project-specific. See [deployment-config.md](../
|
|
|
48
161
|
- Confirm deployment succeeded on production
|
|
49
162
|
- Smoke-test production URLs for critical pages
|
|
50
163
|
- Monitor error rates and performance metrics post-release
|
|
51
|
-
-
|
|
164
|
+
- Have rollback steps documented and ready (see § Rollback Procedures)
|
|
165
|
+
|
|
166
|
+
## Rollback Procedures
|
|
167
|
+
|
|
168
|
+
**Two rollback strategies** (prefer platform-level when available):
|
|
169
|
+
|
|
170
|
+
1. **Platform rollback** — promote the last known-good deployment from the hosting dashboard
|
|
171
|
+
2. **Git revert** — `git revert -m 1 HEAD && git push origin main` (triggers a clean redeploy)
|
|
172
|
+
|
|
173
|
+
### Rollback Checklist
|
|
174
|
+
|
|
175
|
+
- [ ] Confirm the issue is deployment-related (not a data or third-party issue)
|
|
176
|
+
- [ ] Roll back via platform or git revert — never force-push to `main`
|
|
177
|
+
- [ ] Verify the rollback deployment is healthy (smoke tests)
|
|
178
|
+
- [ ] Notify the team with a summary of what was rolled back and why
|
|
179
|
+
- [ ] Create a post-mortem ticket to investigate root cause
|
|
180
|
+
|
|
181
|
+
## Anti-Patterns
|
|
182
|
+
|
|
183
|
+
| Anti-Pattern | Why It's Wrong | Correct Approach |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| Hardcoding secrets in source code | Secrets leak via git history, logs, and client bundles | Use environment variables; validate with Zod at startup |
|
|
186
|
+
| Skipping preview deployments | Bugs reach production without visual review | Deploy every branch to a preview environment |
|
|
187
|
+
| Using `Cache-Control: no-store` everywhere | Destroys performance; every request hits origin | Use appropriate cache durations per asset type (see table above) |
|
|
188
|
+
| Force-pushing to `main` to "fix" a bad deploy | Destroys git history; breaks other developers' branches | Use `git revert` to cleanly undo changes |
|
|
189
|
+
| Disabling security headers "temporarily" | Temporary becomes permanent; opens attack surface | Keep headers strict; expand only with documented exceptions |
|
|
190
|
+
| Running builds without `--frozen-lockfile` | Non-deterministic installs; works locally, fails in CI | Always use `--frozen-lockfile` (or equivalent) in CI |
|
|
191
|
+
| Storing `.env.local` in the repository | Developer secrets and tokens leak to all contributors | Add `.env.local` to `.gitignore`; share via secure vault |
|
|
192
|
+
| No startup validation of env vars | App starts but crashes later with cryptic errors | Validate all required variables at boot (fail fast) |
|
|
@@ -38,11 +38,9 @@ Generic documentation templates and writing standards. For project-specific dire
|
|
|
38
38
|
## Roadmap Update Template
|
|
39
39
|
|
|
40
40
|
When a feature is completed:
|
|
41
|
-
1. Change status to `COMPLETE`
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4. Update the summary table at the top
|
|
45
|
-
5. Move to completed section if applicable
|
|
41
|
+
1. Change status to `COMPLETE` and add completion date
|
|
42
|
+
2. List modified files and update the summary table
|
|
43
|
+
3. Move to completed section if applicable
|
|
46
44
|
|
|
47
45
|
## Architecture Decision Record Template
|
|
48
46
|
|
|
@@ -57,16 +55,107 @@ When a feature is completed:
|
|
|
57
55
|
**Alternatives Considered:** [What else was evaluated]
|
|
58
56
|
```
|
|
59
57
|
|
|
58
|
+
## README Template
|
|
59
|
+
|
|
60
|
+
Use this structure for library or feature-level READMEs:
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
# Feature / Library Name
|
|
64
|
+
|
|
65
|
+
One-sentence summary of what this does and why it exists.
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
Brief usage example or setup steps.
|
|
70
|
+
|
|
71
|
+
## Architecture
|
|
72
|
+
|
|
73
|
+
High-level overview. Include a Mermaid diagram for non-trivial systems.
|
|
74
|
+
|
|
75
|
+
## Key Files
|
|
76
|
+
|
|
77
|
+
| File | Purpose |
|
|
78
|
+
|------|---------|
|
|
79
|
+
| `src/handler.ts` | Request handling logic |
|
|
80
|
+
| `src/schema.ts` | Validation schemas |
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Mermaid Diagram Patterns
|
|
84
|
+
|
|
85
|
+
Keep diagrams focused — one concern per diagram.
|
|
86
|
+
### Flowchart (decision logic, pipelines)
|
|
87
|
+
|
|
88
|
+
```mermaid
|
|
89
|
+
flowchart TD
|
|
90
|
+
A[Receive Request] --> B{Authenticated?}
|
|
91
|
+
B -- Yes --> C[Process Request]
|
|
92
|
+
B -- No --> D[Return 401]
|
|
93
|
+
C --> E{Valid Input?}
|
|
94
|
+
E -- Yes --> F[Execute Action]
|
|
95
|
+
E -- No --> G[Return 400]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Sequence Diagram (API flows, multi-service interactions)
|
|
99
|
+
|
|
100
|
+
```mermaid
|
|
101
|
+
sequenceDiagram
|
|
102
|
+
participant Client
|
|
103
|
+
participant API
|
|
104
|
+
participant DB
|
|
105
|
+
Client->>API: POST /resource
|
|
106
|
+
API->>DB: INSERT record
|
|
107
|
+
DB-->>API: OK
|
|
108
|
+
API-->>Client: 201 Created
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### ER Diagram (data models, relationships)
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
erDiagram
|
|
115
|
+
USER ||--o{ ORDER : places
|
|
116
|
+
ORDER ||--|{ LINE_ITEM : contains
|
|
117
|
+
USER { string id PK; string email }
|
|
118
|
+
ORDER { string id PK; date created_at }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Diagram Guidelines
|
|
122
|
+
|
|
123
|
+
- Add a title comment (`%% Title: ...`) at the top of complex diagrams
|
|
124
|
+
- Use verb labels on relationship arrows
|
|
125
|
+
- Limit nodes to 10–12 per diagram; split larger systems into sub-diagrams
|
|
126
|
+
- Prefer `flowchart TD` (top-down) for pipelines and `flowchart LR` (left-right) for request flows
|
|
127
|
+
|
|
128
|
+
## Changelog Entry Template
|
|
129
|
+
|
|
130
|
+
Follow Conventional Commits format. Group entries by type under a version heading:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
## [1.2.0] — YYYY-MM-DD
|
|
134
|
+
|
|
135
|
+
### Added
|
|
136
|
+
- feat: Add retry logic to API client (#123)
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
- fix: Resolve race condition in queue processor (#127)
|
|
140
|
+
|
|
141
|
+
### Changed
|
|
142
|
+
- refactor: Extract validation into shared module (#125)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Changelog Rules
|
|
146
|
+
|
|
147
|
+
- One line per change; reference the PR or issue number
|
|
148
|
+
- Use imperative mood: "Add", "Fix", "Remove" — not "Added", "Fixed"
|
|
149
|
+
- Group under `Added`, `Fixed`, `Changed`, `Removed`, `Deprecated`, `Security`
|
|
150
|
+
- Most recent version at the top
|
|
151
|
+
|
|
60
152
|
## Writing Guidelines
|
|
61
153
|
|
|
62
154
|
- Write clear, concise prose — avoid jargon unless necessary
|
|
63
|
-
- Include
|
|
64
|
-
- Link to related files
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
- Include "Last Updated" dates on all documents
|
|
68
|
-
- Archive outdated docs rather than deleting
|
|
69
|
-
- Cross-reference between documents when relevant
|
|
155
|
+
- Include Mermaid diagrams for architecture
|
|
156
|
+
- Link to related files using relative paths
|
|
157
|
+
- Use tables for structured data; include "Last Updated" dates on all documents
|
|
158
|
+
- Archive outdated docs rather than deleting; cross-reference between documents
|
|
70
159
|
|
|
71
160
|
### Formatting Rules
|
|
72
161
|
|
|
@@ -85,3 +174,27 @@ Include YAML front matter at the beginning of instruction/skill files:
|
|
|
85
174
|
- `title` / `name`: The title of the document
|
|
86
175
|
- `description`: A brief description of the document content
|
|
87
176
|
- `applyTo`: (for instruction files) Glob pattern for which files the instructions apply to
|
|
177
|
+
|
|
178
|
+
## Documentation Review Checklist
|
|
179
|
+
|
|
180
|
+
Before merging docs, verify:
|
|
181
|
+
|
|
182
|
+
- [ ] **Accuracy** — all code snippets, file paths, and commands are correct and tested
|
|
183
|
+
- [ ] **Completeness** — no TODO placeholders or empty sections remain
|
|
184
|
+
- [ ] **Links** — all internal and external links resolve (no 404s)
|
|
185
|
+
- [ ] **Front matter** — YAML front matter is present and valid
|
|
186
|
+
- [ ] **Formatting** — consistent heading levels, list style, whitespace, and Mermaid renders
|
|
187
|
+
- [ ] **Cross-references** — related docs link to each other; "Last Updated" is current
|
|
188
|
+
|
|
189
|
+
## Anti-Patterns
|
|
190
|
+
|
|
191
|
+
| Anti-Pattern | Why It's Bad | Do This Instead |
|
|
192
|
+
|-------------|-------------|-----------------|
|
|
193
|
+
| Wall of text with no headings | Unnavigable; readers skip it | Break into sections with H2/H3 |
|
|
194
|
+
| Duplicating content across files | Copies drift; causes confusion | Link to a single source of truth |
|
|
195
|
+
| Screenshots without alt text | Inaccessible; breaks when UI changes | Use Mermaid diagrams or describe the UI |
|
|
196
|
+
| Documenting implementation details | Becomes stale as code changes | Document intent and contracts |
|
|
197
|
+
| Using absolute file paths | Breaks on other machines | Use relative paths from doc location |
|
|
198
|
+
| Huge monolithic README | Low signal-to-noise | Split into focused docs, link from README |
|
|
199
|
+
| Undated documents | No way to judge currency | Always include "Last Updated" date |
|
|
200
|
+
| Using H1 inside document body | Conflicts with auto-generated title | Start body headings at H2 |
|
|
@@ -41,4 +41,155 @@ Interpret creatively and make unexpected choices that feel genuinely designed fo
|
|
|
41
41
|
|
|
42
42
|
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
|
43
43
|
|
|
44
|
-
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
|
44
|
+
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
|
45
|
+
|
|
46
|
+
## Design System Foundations
|
|
47
|
+
|
|
48
|
+
Every design starts with a token layer. Define CSS custom properties that encode the aesthetic direction — never scatter raw values through stylesheets. A well-structured variable system makes the entire interface feel cohesive even as complexity grows.
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
/* --- Palette: warm editorial with a punch of citron --- */
|
|
52
|
+
:root {
|
|
53
|
+
--color-ink: #1a1614;
|
|
54
|
+
--color-paper: #f5f0e8;
|
|
55
|
+
--color-accent: #c8e630; /* citron — the memorable detail */
|
|
56
|
+
--color-muted: #9b9083;
|
|
57
|
+
--color-surface: #eae3d8;
|
|
58
|
+
--color-border: rgba(26, 22, 20, 0.08);
|
|
59
|
+
|
|
60
|
+
/* Typography scale — modular ratio 1.25 (Major Third) */
|
|
61
|
+
--text-sm: clamp(0.875rem, 0.83rem + 0.22vw, 1rem);
|
|
62
|
+
--text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
|
|
63
|
+
--text-xl: clamp(1.563rem, 1.35rem + 1.06vw, 2rem);
|
|
64
|
+
--text-2xl: clamp(1.953rem, 1.6rem + 1.77vw, 2.75rem);
|
|
65
|
+
--text-hero: clamp(2.441rem, 1.8rem + 3.2vw, 4.5rem);
|
|
66
|
+
|
|
67
|
+
/* Spacing — 4px base, geometric progression */
|
|
68
|
+
--space-2: 0.5rem; --space-4: 1rem;
|
|
69
|
+
--space-6: 1.5rem; --space-8: 2rem;
|
|
70
|
+
--space-16: 4rem; --space-32: 8rem;
|
|
71
|
+
|
|
72
|
+
/* Motion — intentional easing curves, not defaults */
|
|
73
|
+
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
74
|
+
--ease-in-out-back: cubic-bezier(0.68, -0.6, 0.32, 1.6);
|
|
75
|
+
--duration-fast: 150ms;
|
|
76
|
+
--duration-normal: 300ms;
|
|
77
|
+
--duration-slow: 600ms;
|
|
78
|
+
|
|
79
|
+
/* Elevation */
|
|
80
|
+
--shadow-md: 0 4px 16px rgba(26, 22, 20, 0.08);
|
|
81
|
+
--shadow-lg: 0 12px 48px rgba(26, 22, 20, 0.12);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Anti-pattern:** Never scatter raw hex/px values through stylesheets. Every value should trace back to a token. Change the palette once and the entire interface follows.
|
|
86
|
+
|
|
87
|
+
## Component Patterns
|
|
88
|
+
|
|
89
|
+
### Distinctive Card
|
|
90
|
+
|
|
91
|
+
A card should never look like a Bootstrap default. Give it tension — an unexpected border treatment, an oversized label, or a hover that reveals hidden depth.
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
.card {
|
|
95
|
+
position: relative;
|
|
96
|
+
background: var(--color-paper);
|
|
97
|
+
border: 1px solid var(--color-border);
|
|
98
|
+
border-left: 4px solid var(--color-accent);
|
|
99
|
+
padding: var(--space-8) var(--space-6);
|
|
100
|
+
transition: transform var(--duration-normal) var(--ease-out-expo),
|
|
101
|
+
box-shadow var(--duration-normal) var(--ease-out-expo);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.card:hover {
|
|
105
|
+
transform: translateY(-3px);
|
|
106
|
+
box-shadow: var(--shadow-lg);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.card__label {
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: calc(-1 * var(--space-3));
|
|
112
|
+
left: var(--space-4);
|
|
113
|
+
background: var(--color-accent);
|
|
114
|
+
color: var(--color-ink);
|
|
115
|
+
font-size: var(--text-xs);
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
letter-spacing: 0.08em;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
padding: var(--space-1) var(--space-3);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Hero Section with Staggered Reveal
|
|
124
|
+
|
|
125
|
+
Orchestrate entrance animations with `animation-delay` for a cinematic first impression. One coordinated sequence beats a dozen scattered `fadeIn`s.
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
@keyframes rise {
|
|
129
|
+
from { opacity: 0; transform: translateY(24px); }
|
|
130
|
+
to { opacity: 1; transform: translateY(0); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.hero { overflow: hidden; padding: var(--space-32) var(--space-8); }
|
|
134
|
+
.hero__eyebrow { animation: rise var(--duration-slow) var(--ease-out-expo) both; animation-delay: 100ms; }
|
|
135
|
+
.hero__headline { animation: rise var(--duration-slow) var(--ease-out-expo) both; animation-delay: 250ms; }
|
|
136
|
+
.hero__body { animation: rise var(--duration-slow) var(--ease-out-expo) both; animation-delay: 400ms; }
|
|
137
|
+
.hero__cta { animation: rise var(--duration-slow) var(--ease-out-expo) both; animation-delay: 550ms; }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Anti-pattern:** Don't animate everything. If the nav bounces, the sidebar slides, and the footer pulses — it's visual noise, not design. Motion has a narrative: one orchestrated entrance, then stillness.
|
|
141
|
+
|
|
142
|
+
## Typography Pairing Examples
|
|
143
|
+
|
|
144
|
+
Never reach for the same font twice. Each project deserves a pairing chosen for its specific character. These are starting points — not a rotation list.
|
|
145
|
+
|
|
146
|
+
| Aesthetic | Display | Body | Mood |
|
|
147
|
+
|-----------|---------|------|------|
|
|
148
|
+
| Editorial luxury | Playfair Display | Source Serif 4 | Authoritative, rich serif contrast |
|
|
149
|
+
| Swiss precision | Darker Grotesque | IBM Plex Sans | Sharp, high-contrast grotesque |
|
|
150
|
+
| Warm humanist | Fraunces | Nunito Sans | Friendly optical sizes, approachable |
|
|
151
|
+
| Brutalist edge | Monument Extended | JetBrains Mono | Wide + monospace = raw technical power |
|
|
152
|
+
| Art nouveau organic | Cormorant Garamond | Lora | Flowing, calligraphic sensibility |
|
|
153
|
+
| Retro-futuristic | Syne | Outfit | Geometric boldness meets clean body |
|
|
154
|
+
|
|
155
|
+
Always include a fallback chain that preserves metrics: `'Fraunces', 'Georgia', serif` not just `'Fraunces', serif`. And never default to the same "safe" choices (Inter, Roboto, system-ui) — if every project looks the same, the typography isn't doing its job.
|
|
156
|
+
|
|
157
|
+
## Design Quality Checklist
|
|
158
|
+
|
|
159
|
+
Run this checklist before delivering any frontend work. Every item is a gate — if something fails, the design isn't finished.
|
|
160
|
+
|
|
161
|
+
### Identity & Cohesion
|
|
162
|
+
- [ ] Can you name the aesthetic direction in 2-3 words? (e.g., "warm editorial," "cold brutalist")
|
|
163
|
+
- [ ] Are color, typography, spacing, and motion all telling the same visual story?
|
|
164
|
+
- [ ] Is there at least one memorable detail — something unexpected that delights?
|
|
165
|
+
|
|
166
|
+
### Typography
|
|
167
|
+
- [ ] Display and body fonts are distinct and intentionally paired
|
|
168
|
+
- [ ] Type scale uses `clamp()` for fluid responsive sizing — no fixed `px` breakpoints
|
|
169
|
+
- [ ] Line heights are tuned: ~1.1–1.2 for headings, ~1.5–1.7 for body
|
|
170
|
+
- [ ] Letter-spacing is adjusted for uppercase text and small sizes
|
|
171
|
+
|
|
172
|
+
### Color & Contrast
|
|
173
|
+
- [ ] Palette is defined as CSS custom properties — no raw hex in component styles
|
|
174
|
+
- [ ] There is a clear dominant/accent hierarchy — not five competing colors
|
|
175
|
+
- [ ] Text passes WCAG AA contrast minimums (4.5:1 body, 3:1 large text)
|
|
176
|
+
- [ ] Dark/light theme (if applicable) is not just color inversion — both feel intentional
|
|
177
|
+
|
|
178
|
+
### Layout & Spacing
|
|
179
|
+
- [ ] Spacing flows from a consistent scale — no random `margin: 37px`
|
|
180
|
+
- [ ] At least one layout choice breaks the expected grid — overlap, bleed, asymmetry
|
|
181
|
+
- [ ] Component padding and gaps use spacing tokens, not ad-hoc values
|
|
182
|
+
- [ ] The design holds at mobile, tablet, and desktop without layout collapse
|
|
183
|
+
|
|
184
|
+
### Motion & Interaction
|
|
185
|
+
- [ ] Page entrance has a coordinated animation sequence (staggered reveals)
|
|
186
|
+
- [ ] Hover/focus states exist for all interactive elements
|
|
187
|
+
- [ ] Animations use custom easing curves — never `linear` or bare `ease`
|
|
188
|
+
- [ ] Motion serves narrative purpose — no decoration-only animation
|
|
189
|
+
- [ ] `prefers-reduced-motion` is respected with a `@media` query fallback
|
|
190
|
+
|
|
191
|
+
### Production Readiness
|
|
192
|
+
- [ ] No hardcoded widths that break at unexpected viewports
|
|
193
|
+
- [ ] Images and decorative elements have proper `alt` text or `aria-hidden`
|
|
194
|
+
- [ ] Focus indicators are visible and styled to match the aesthetic
|
|
195
|
+
- [ ] Performance: no layout thrashing from scroll-triggered animations without `will-change`
|
|
@@ -9,11 +9,9 @@ description: "Next.js App Router best practices for server/client components, ro
|
|
|
9
9
|
|
|
10
10
|
## Project Structure
|
|
11
11
|
|
|
12
|
-
- **Use `app/` directory** (App Router) for all routes.
|
|
12
|
+
- **Use `app/` directory** (App Router) for all routes; colocate files near where they're used.
|
|
13
13
|
- Top-level: `app/`, `public/`, `lib/`, `components/`, `contexts/`, `styles/`, `hooks/`, `types/`.
|
|
14
|
-
-
|
|
15
|
-
- **Route Groups**: parentheses `(admin)` — group without affecting URL.
|
|
16
|
-
- **Private Folders**: underscore `_internal` — opt out of routing.
|
|
14
|
+
- **Route Groups** `(admin)` — group without affecting URL. **Private Folders** `_internal` — opt out of routing.
|
|
17
15
|
- Feature folders for large apps: `app/dashboard/`, `app/auth/`.
|
|
18
16
|
|
|
19
17
|
## Server and Client Components
|
|
@@ -22,39 +20,116 @@ description: "Next.js App Router best practices for server/client components, ro
|
|
|
22
20
|
|
|
23
21
|
**Client Components** — add `'use client'` at top. Use for interactivity, state, browser APIs.
|
|
24
22
|
|
|
23
|
+
### Decision Table
|
|
24
|
+
|
|
25
|
+
| Need | Component Type | Why |
|
|
26
|
+
|------|---------------|-----|
|
|
27
|
+
| Fetch data at request time | Server | Direct DB/API access, no client waterfall |
|
|
28
|
+
| Read cookies/headers | Server | Available only on the server |
|
|
29
|
+
| Interactive UI (clicks, inputs) | Client | Requires event handlers |
|
|
30
|
+
| Use `useState` / `useEffect` | Client | React hooks need client runtime |
|
|
31
|
+
| Access browser APIs (localStorage, geolocation) | Client | Not available on server |
|
|
32
|
+
| Render static/non-interactive content | Server | Smaller bundle, faster paint |
|
|
33
|
+
| Show loading spinners for async children | Server (with `<Suspense>`) | Streams HTML progressively |
|
|
34
|
+
|
|
25
35
|
### Critical Rule
|
|
26
36
|
|
|
27
37
|
**Never use `next/dynamic` with `{ ssr: false }` inside a Server Component.** This causes build/runtime errors.
|
|
28
38
|
|
|
29
|
-
**Correct approach:**
|
|
30
|
-
1. Move all client-only logic into a dedicated Client Component (`'use client'`).
|
|
31
|
-
2. Import and use that Client Component directly in the Server Component.
|
|
39
|
+
**Correct approach:** Move client-only logic into a dedicated `'use client'` component, then import it normally.
|
|
32
40
|
|
|
33
41
|
```tsx
|
|
34
|
-
// Server Component
|
|
42
|
+
// Server Component — imports a Client Component directly
|
|
35
43
|
import DashboardNavbar from '@/components/DashboardNavbar';
|
|
36
|
-
|
|
37
44
|
export default async function DashboardPage() {
|
|
38
|
-
return
|
|
39
|
-
<>
|
|
40
|
-
<DashboardNavbar /> {/* Client Component */}
|
|
41
|
-
</>
|
|
42
|
-
);
|
|
45
|
+
return <><DashboardNavbar /></>;
|
|
43
46
|
}
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
##
|
|
49
|
+
## Data Fetching Patterns
|
|
50
|
+
|
|
51
|
+
### Server-Side Fetching
|
|
52
|
+
|
|
53
|
+
Fetch directly in `async` Server Components. Next.js deduplicates identical `fetch` calls.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
export default async function ProjectsPage() {
|
|
57
|
+
const projects = await fetch('https://api.example.com/projects', {
|
|
58
|
+
next: { revalidate: 60 }, // ISR: revalidate every 60s
|
|
59
|
+
}).then((res) => res.json());
|
|
60
|
+
return <ul>{projects.map((p: { id: string; name: string }) => <li key={p.id}>{p.name}</li>)}</ul>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Server Actions (mutations)
|
|
65
|
+
|
|
66
|
+
Define with `'use server'`. Call from Client Components via `action` or `startTransition`.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// lib/actions.ts
|
|
70
|
+
'use server';
|
|
71
|
+
import { revalidatePath } from 'next/cache';
|
|
72
|
+
export async function createItem(formData: FormData) {
|
|
73
|
+
const name = formData.get('name') as string;
|
|
74
|
+
await db.items.create({ data: { name } });
|
|
75
|
+
revalidatePath('/items');
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// components/CreateItemForm.tsx — calls the Server Action
|
|
81
|
+
'use client';
|
|
82
|
+
import { createItem } from '@/lib/actions';
|
|
83
|
+
export default function CreateItemForm() {
|
|
84
|
+
return <form action={createItem}><input name="name" required /><button type="submit">Add</button></form>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
Each route segment can export `error.tsx` (must be a Client Component) and `not-found.tsx`.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// app/dashboard/error.tsx — must be a Client Component
|
|
94
|
+
'use client';
|
|
95
|
+
export default function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
|
|
96
|
+
return <div role="alert"><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// app/projects/[id]/page.tsx — use notFound() to trigger not-found.tsx
|
|
102
|
+
import { notFound } from 'next/navigation';
|
|
103
|
+
export default async function ProjectPage({ params }: { params: { id: string } }) {
|
|
104
|
+
const project = await getProject(params.id);
|
|
105
|
+
if (!project) notFound();
|
|
106
|
+
return <h1>{project.name}</h1>;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
47
109
|
|
|
48
|
-
|
|
110
|
+
## Middleware
|
|
111
|
+
|
|
112
|
+
Place `middleware.ts` at the project root (next to `app/`). Runs before every matched request.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
116
|
+
export function middleware(request: NextRequest) {
|
|
117
|
+
const token = request.cookies.get('session')?.value;
|
|
118
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
119
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
120
|
+
}
|
|
121
|
+
return NextResponse.next();
|
|
122
|
+
}
|
|
123
|
+
export const config = { matcher: ['/dashboard/:path*', '/settings/:path*'] };
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Component Practices & Naming
|
|
127
|
+
|
|
128
|
+
- PascalCase for component files/exports. camelCase for hooks.
|
|
49
129
|
- Shared components in `components/`. Route-specific in route folder.
|
|
50
130
|
- TypeScript interfaces for props. Explicit types and defaults.
|
|
51
131
|
- Co-locate tests with components.
|
|
52
|
-
|
|
53
|
-
## Naming Conventions
|
|
54
|
-
|
|
55
|
-
- Folders: `kebab-case`.
|
|
56
|
-
- Components: `PascalCase`. Hooks: `camelCase`. Assets: `kebab-case`.
|
|
57
|
-
- Types/Interfaces: `PascalCase`. Constants: `UPPER_SNAKE_CASE`.
|
|
132
|
+
- Folders: `kebab-case`. Types/Interfaces: `PascalCase`. Constants: `UPPER_SNAKE_CASE`.
|
|
58
133
|
|
|
59
134
|
## API Routes (Route Handlers)
|
|
60
135
|
|
|
@@ -65,13 +140,61 @@ export default async function DashboardPage() {
|
|
|
65
140
|
- Validate with Zod/Yup. Return appropriate status codes.
|
|
66
141
|
- Protect sensitive routes with middleware or server-side session checks.
|
|
67
142
|
|
|
143
|
+
## Performance Patterns
|
|
144
|
+
|
|
145
|
+
### Dynamic Imports (Client Components only)
|
|
146
|
+
|
|
147
|
+
Lazy-load heavy Client Components to reduce initial bundle size.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
'use client';
|
|
151
|
+
import dynamic from 'next/dynamic';
|
|
152
|
+
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
|
|
153
|
+
loading: () => <p>Loading chart…</p>,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Parallel Data Fetching
|
|
158
|
+
|
|
159
|
+
Initiate independent fetches simultaneously — don't `await` sequentially.
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
export default async function DashboardPage() {
|
|
163
|
+
const [metrics, activity] = await Promise.all([getMetrics(), getRecentActivity()]);
|
|
164
|
+
return <><MetricsPanel data={metrics} /><ActivityFeed items={activity} /></>;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Streaming with Suspense
|
|
169
|
+
|
|
170
|
+
Wrap slow data sections in `<Suspense>` so the shell renders immediately.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { Suspense } from 'react';
|
|
174
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
175
|
+
return <main><Suspense fallback={<p>Loading…</p>}>{children}</Suspense></main>;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
68
179
|
## General Best Practices
|
|
69
180
|
|
|
70
|
-
- TypeScript with `strict` mode.
|
|
71
|
-
- ESLint with official Next.js config.
|
|
181
|
+
- TypeScript with `strict` mode. ESLint with official Next.js config.
|
|
72
182
|
- Secrets in `.env.local` — never committed.
|
|
73
183
|
- Built-in Image and Font optimization.
|
|
74
184
|
- Suspense and loading states for async data.
|
|
75
185
|
- Avoid large client bundles — keep logic in Server Components.
|
|
76
186
|
- Semantic HTML and ARIA attributes.
|
|
77
187
|
- Do NOT create example/demo files unless explicitly requested.
|
|
188
|
+
|
|
189
|
+
## Anti-Patterns
|
|
190
|
+
|
|
191
|
+
| Anti-Pattern | Why It's Wrong | Do This Instead |
|
|
192
|
+
|-------------|---------------|-----------------|
|
|
193
|
+
| `'use client'` on every component | Bloats JS bundle, defeats RSC benefits | Default to Server Components; add `'use client'` only when needed |
|
|
194
|
+
| Sequential `await` for independent data | Creates a waterfall, slows page load | Use `Promise.all()` for parallel fetches |
|
|
195
|
+
| `next/dynamic` with `ssr: false` in Server Components | Build/runtime crash | Extract to a Client Component, import normally |
|
|
196
|
+
| Fetching in `useEffect` when server fetch works | Extra client roundtrip, loading flash | Fetch in the Server Component or use Server Actions |
|
|
197
|
+
| Giant `layout.tsx` with all providers | Hard to test, couples unrelated concerns | Split providers into a `Providers` Client Component |
|
|
198
|
+
| Catching errors without `error.tsx` | Unhandled errors crash the page | Add `error.tsx` per route segment |
|
|
199
|
+
| Hardcoding secrets in source files | Security risk, leaks in version control | Use `.env.local` and `process.env` |
|
|
200
|
+
| Skipping `loading.tsx` / `<Suspense>` | Blank screen while data loads | Add `loading.tsx` or wrap in `<Suspense>` |
|
|
@@ -7,36 +7,194 @@ description: "Technical SEO patterns for meta tags, structured data, sitemaps, U
|
|
|
7
7
|
|
|
8
8
|
# SEO Patterns
|
|
9
9
|
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
- Every public page MUST have a unique `<title>` and `<meta name="description">`.
|
|
13
|
+
- Structured data MUST validate against Google's Rich Results Test before shipping.
|
|
14
|
+
- Server-render all content critical for indexing — never rely on client-side JS for primary content.
|
|
15
|
+
- Canonical URLs are mandatory on every page to prevent duplicate content issues.
|
|
16
|
+
|
|
10
17
|
## Meta Tags & Open Graph
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
|
|
19
|
+
Every page template must include the full set of meta tags:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Next.js App Router — layout or page metadata
|
|
23
|
+
export const metadata: Metadata = {
|
|
24
|
+
title: 'Product Name — Short Descriptor',
|
|
25
|
+
description: 'Concise 150-160 char description with primary keyword.',
|
|
26
|
+
alternates: { canonical: 'https://example.com/page-slug' },
|
|
27
|
+
openGraph: {
|
|
28
|
+
title: 'Product Name — Short Descriptor',
|
|
29
|
+
description: 'Concise description for social sharing.',
|
|
30
|
+
url: 'https://example.com/page-slug',
|
|
31
|
+
type: 'website',
|
|
32
|
+
images: [{ url: 'https://example.com/og-image.jpg', width: 1200, height: 630 }],
|
|
33
|
+
},
|
|
34
|
+
twitter: {
|
|
35
|
+
card: 'summary_large_image',
|
|
36
|
+
title: 'Product Name — Short Descriptor',
|
|
37
|
+
images: ['https://example.com/og-image.jpg'],
|
|
38
|
+
},
|
|
39
|
+
robots: { index: true, follow: true },
|
|
40
|
+
};
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Meta Tag Checklist
|
|
44
|
+
|
|
45
|
+
- [ ] `<title>` is unique, 50-60 chars, includes primary keyword
|
|
46
|
+
- [ ] `<meta name="description">` is unique, 150-160 chars, includes CTA
|
|
47
|
+
- [ ] `<link rel="canonical">` points to the single authoritative URL
|
|
48
|
+
- [ ] `og:title`, `og:description`, `og:image` (1200×630 px min), `og:type` are set
|
|
49
|
+
- [ ] `twitter:card`, `twitter:title`, `twitter:image` are set
|
|
50
|
+
- [ ] `robots` directives are correct (`noindex` on admin/draft pages only)
|
|
16
51
|
|
|
17
52
|
## Structured Data (JSON-LD)
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
53
|
+
|
|
54
|
+
Use JSON-LD `<script>` blocks — never microdata or RDFa. Choose schema types based on page purpose:
|
|
55
|
+
|
|
56
|
+
| Page Type | Schema Type(s) | Required Properties |
|
|
57
|
+
|-----------|----------------|---------------------|
|
|
58
|
+
| Homepage | `WebSite`, `Organization` | `name`, `url`, `searchAction`, `logo` |
|
|
59
|
+
| Detail page | `Product`, `Article`, or domain-specific type | `name`, `description`, `image` |
|
|
60
|
+
| Listing / category page | `ItemList` + `ListItem` | `itemListElement`, `position`, `url` |
|
|
61
|
+
| Breadcrumb navigation | `BreadcrumbList` | `itemListElement`, `position`, `name` |
|
|
62
|
+
| Blog post | `Article` or `BlogPosting` | `headline`, `datePublished`, `author` |
|
|
63
|
+
| FAQ page | `FAQPage` | `mainEntity` with `Question` + `Answer` |
|
|
64
|
+
|
|
65
|
+
### Example: Breadcrumb + Article
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
function StructuredData({ breadcrumbs, article }: Props) {
|
|
69
|
+
const breadcrumbLd = {
|
|
70
|
+
'@context': 'https://schema.org',
|
|
71
|
+
'@type': 'BreadcrumbList',
|
|
72
|
+
itemListElement: breadcrumbs.map((crumb, i) => ({
|
|
73
|
+
'@type': 'ListItem',
|
|
74
|
+
position: i + 1,
|
|
75
|
+
name: crumb.label,
|
|
76
|
+
item: crumb.url,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const articleLd = {
|
|
81
|
+
'@context': 'https://schema.org',
|
|
82
|
+
'@type': 'Article',
|
|
83
|
+
headline: article.title,
|
|
84
|
+
description: article.summary,
|
|
85
|
+
image: article.imageUrl,
|
|
86
|
+
datePublished: article.publishedAt,
|
|
87
|
+
dateModified: article.updatedAt,
|
|
88
|
+
author: { '@type': 'Person', name: article.author },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
|
|
94
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(articleLd) }} />
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Validation
|
|
101
|
+
|
|
102
|
+
- Run every JSON-LD block through [Google's Rich Results Test](https://search.google.com/test/rich-results) before merging.
|
|
103
|
+
- Validate against [schema.org](https://schema.org) definitions for required/recommended properties.
|
|
104
|
+
- Check the Search Console **Enhancements** report after deployment.
|
|
24
105
|
|
|
25
106
|
## Sitemap & Crawlability
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
107
|
+
|
|
108
|
+
- Generate an XML sitemap dynamically from your data source (CMS, database, filesystem).
|
|
109
|
+
- Use a **sitemap index** when page count exceeds 50,000 URLs or file size exceeds 50 MB.
|
|
110
|
+
- Include `<lastmod>` timestamps — omit if you can't guarantee accuracy.
|
|
111
|
+
- Submit sitemaps via Google Search Console and reference them in `robots.txt`.
|
|
112
|
+
|
|
113
|
+
### Example: robots.txt
|
|
114
|
+
|
|
115
|
+
```txt
|
|
116
|
+
User-agent: *
|
|
117
|
+
Allow: /
|
|
118
|
+
Disallow: /admin/
|
|
119
|
+
Disallow: /api/
|
|
120
|
+
Disallow: /preview/
|
|
121
|
+
|
|
122
|
+
Sitemap: https://example.com/sitemap.xml
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Crawlability Checklist
|
|
126
|
+
|
|
127
|
+
- [ ] `robots.txt` allows crawling of all public pages
|
|
128
|
+
- [ ] `robots.txt` blocks admin, API, and preview routes
|
|
129
|
+
- [ ] XML sitemap is auto-generated and up to date
|
|
130
|
+
- [ ] Sitemap is referenced in `robots.txt`
|
|
131
|
+
- [ ] Internal links connect all public pages (no orphan pages)
|
|
132
|
+
- [ ] Page load time < 3s (crawl budget efficiency)
|
|
31
133
|
|
|
32
134
|
## URL Strategy
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
135
|
+
|
|
136
|
+
- Use lowercase, hyphen-separated slugs: `/blog/my-post-title`
|
|
137
|
+
- Keep URLs short, keyword-relevant, and human-readable.
|
|
138
|
+
- Enforce trailing-slash consistency (pick one, redirect the other).
|
|
139
|
+
- Implement 301 redirects for any renamed or moved pages.
|
|
140
|
+
|
|
141
|
+
| Pattern | Good | Bad |
|
|
142
|
+
|---------|------|-----|
|
|
143
|
+
| Slug format | `/products/blue-widget` | `/products/Blue_Widget` |
|
|
144
|
+
| Hierarchy | `/blog/2026/seo-tips` | `/blog?id=42` |
|
|
145
|
+
| Consistency | Always `/path/` or `/path` | Mixed trailing slashes |
|
|
146
|
+
| Parameters | `/products?sort=price` | `/products/sort/price/asc` |
|
|
147
|
+
|
|
148
|
+
### Redirect Example (Next.js)
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// next.config.ts
|
|
152
|
+
const nextConfig = {
|
|
153
|
+
async redirects() {
|
|
154
|
+
return [
|
|
155
|
+
{ source: '/old-page', destination: '/new-page', permanent: true },
|
|
156
|
+
{ source: '/blog/:slug/amp', destination: '/blog/:slug', permanent: true },
|
|
157
|
+
];
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
```
|
|
37
161
|
|
|
38
162
|
## Rendering & Indexability
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
163
|
+
|
|
164
|
+
- **Server-render** all content that must be indexed — titles, descriptions, body text, structured data.
|
|
165
|
+
- **Client-hydrated** interactive elements (filters, modals) are fine, but content behind JS-only rendering will not be indexed reliably.
|
|
166
|
+
- Use semantic HTML (`<h1>`–`<h6>`, `<article>`, `<nav>`, `<main>`) for crawlers to understand page structure.
|
|
167
|
+
|
|
168
|
+
### Image SEO
|
|
169
|
+
|
|
170
|
+
| Attribute | Purpose | Example |
|
|
171
|
+
|-----------|---------|---------|
|
|
172
|
+
| `alt` | Describes image for screen readers + crawlers | `alt="Blue widget on white background"` |
|
|
173
|
+
| `loading` | Lazy-load below-fold images | `loading="lazy"` |
|
|
174
|
+
| `width` / `height` | Prevents layout shift (CLS) | `width={800} height={600}` |
|
|
175
|
+
| File name | Keyword signal | `blue-widget-front.webp` |
|
|
176
|
+
| Format | Performance + quality | Use WebP/AVIF with JPEG fallback |
|
|
177
|
+
|
|
178
|
+
### Indexability Checklist
|
|
179
|
+
|
|
180
|
+
- [ ] Primary content renders in initial HTML (view source, not inspect)
|
|
181
|
+
- [ ] `<h1>` is unique per page and contains the primary keyword
|
|
182
|
+
- [ ] Structured data is present in the server-rendered HTML
|
|
183
|
+
- [ ] Images have descriptive `alt` text
|
|
184
|
+
- [ ] No `noindex` on pages that should be indexed
|
|
185
|
+
- [ ] Hydration does not remove or rewrite structured data scripts
|
|
186
|
+
|
|
187
|
+
## Anti-Patterns
|
|
188
|
+
|
|
189
|
+
| Anti-Pattern | Why It's Bad | Correct Approach |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Duplicate `<title>` across pages | Dilutes ranking signals; confuses crawlers | Unique, keyword-specific title per page |
|
|
192
|
+
| Missing canonical URL | Causes duplicate content penalties | Add `<link rel="canonical">` to every page |
|
|
193
|
+
| Client-only rendered content | Googlebot may not execute JS reliably | Server-render all indexable content |
|
|
194
|
+
| Hardcoded sitemap file | Goes stale as pages are added/removed | Generate sitemap dynamically from data source |
|
|
195
|
+
| Using `noindex` as a "temporary" fix | Often forgotten; pages stay de-indexed | Fix the underlying issue instead |
|
|
196
|
+
| Stuffing keywords in meta tags | Penalized by search engines | Write natural, user-focused descriptions |
|
|
197
|
+
| Missing `alt` text on images | Lost image search traffic + accessibility failure | Descriptive alt text on every meaningful image |
|
|
198
|
+
| Structured data without validation | Silent errors cause rich result loss | Validate with Google Rich Results Test before merge |
|
|
199
|
+
| Blocking CSS/JS in `robots.txt` | Prevents Googlebot from rendering the page | Only block admin/API routes |
|
|
200
|
+
| Mixed trailing slash URLs | Splits link equity between two URLs | Pick one convention, 301-redirect the other |
|