@skill-graph/cli 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +247 -0
- package/LICENSE +200 -0
- package/NOTICE +62 -0
- package/README.md +398 -0
- package/SKILL_GRAPH.md +443 -0
- package/bin/skill-graph.js +374 -0
- package/docs/ADOPTION.md +117 -0
- package/docs/CONFORMANCE.md +66 -0
- package/docs/PRIMER.md +384 -0
- package/docs/QUICKSTART-30MIN.md +333 -0
- package/docs/ROUTING-METRICS.md +120 -0
- package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
- package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
- package/docs/SKILL_AUDIT_LOOP.md +195 -0
- package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
- package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
- package/docs/adr/0001-predicate-set.md +69 -0
- package/docs/adr/0002-json-ld-context.md +82 -0
- package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
- package/docs/adr/0004-persistent-identifiers.md +74 -0
- package/docs/adr/0005-freshness-consolidation.md +70 -0
- package/docs/adr/0006-revise-predicate-rename.md +105 -0
- package/docs/adr/0007-audit-loop-cadence.md +99 -0
- package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
- package/docs/category-consumers.md +168 -0
- package/docs/concept-map.md +194 -0
- package/docs/diagrams/drift-states.mmd +21 -0
- package/docs/diagrams/manifest-pipeline.mmd +25 -0
- package/docs/diagrams/routing-harness.mmd +41 -0
- package/docs/diagrams/starter-graph.mmd +53 -0
- package/docs/field-decision-guide.md +315 -0
- package/docs/field-rationale.md +211 -0
- package/docs/field-reference.generated.md +624 -0
- package/docs/field-reference.md +1426 -0
- package/docs/glossary.md +190 -0
- package/docs/head-noun-glossary.md +63 -0
- package/docs/images/audit-phases.png +0 -0
- package/docs/images/drift-states.png +0 -0
- package/docs/images/graded-mode.png +0 -0
- package/docs/images/manifest-pipeline.png +0 -0
- package/docs/images/routing-harness.png +0 -0
- package/docs/images/skill-anatomy.png +0 -0
- package/docs/images/starter-graph.png +0 -0
- package/docs/images/system-model.png +0 -0
- package/docs/integrations/github-actions.md +155 -0
- package/docs/manifest-field-mapping.md +443 -0
- package/docs/marketplace-publication-queue.generated.md +240 -0
- package/docs/marketplace-release-agent-prompt.md +82 -0
- package/docs/marketplace-skill-candidate-list.md +272 -0
- package/docs/marketplace-syndication.md +222 -0
- package/docs/migration-sample-review.md +155 -0
- package/docs/migrations/v4-to-v5.md +168 -0
- package/docs/migrations/v5-to-v6.md +221 -0
- package/docs/name-exceptions.yaml +37 -0
- package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
- package/docs/plans/multi-root-workspace.md +148 -0
- package/docs/plans/scripts-roadmap.md +107 -0
- package/docs/plans/v4-schema-bump.md +160 -0
- package/docs/plans/wave-2-extraction.md +122 -0
- package/docs/positioning-vs-marketplaces.md +175 -0
- package/docs/proposals/skill-audit-loop-positioning.md +160 -0
- package/docs/quality-doctrine.md +138 -0
- package/docs/recommended-skills.md +150 -0
- package/docs/research/skill-comprehension-eval-research.md +1830 -0
- package/docs/research/skill-retrieval-evidence.md +66 -0
- package/docs/skill-metadata-protocol.md +471 -0
- package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
- package/examples/audits/a11y/findings.md +52 -0
- package/examples/audits/a11y/scorecard.md +21 -0
- package/examples/audits/a11y/verdict.md +44 -0
- package/examples/audits/debugging/findings.md +59 -0
- package/examples/audits/debugging/scorecard.md +22 -0
- package/examples/audits/debugging/verdict.md +33 -0
- package/examples/audits/documentation/findings.md +59 -0
- package/examples/audits/documentation/scorecard.md +22 -0
- package/examples/audits/documentation/verdict.md +33 -0
- package/examples/evals/a11y.json +140 -0
- package/examples/evals/api-design.json +52 -0
- package/examples/evals/code-review.json +52 -0
- package/examples/evals/data-modeling.json +52 -0
- package/examples/evals/database-migration.json +52 -0
- package/examples/evals/debugging.json +118 -0
- package/examples/evals/dependency-architecture.json +52 -0
- package/examples/evals/design-system-architecture.json +52 -0
- package/examples/evals/error-tracking.json +52 -0
- package/examples/evals/event-contract-design.json +52 -0
- package/examples/evals/form-ux-architecture.json +52 -0
- package/examples/evals/framework-fit-analysis.json +52 -0
- package/examples/evals/graph-audit.json +139 -0
- package/examples/evals/information-architecture.json +52 -0
- package/examples/evals/interaction-feedback.json +52 -0
- package/examples/evals/interaction-patterns.json +52 -0
- package/examples/evals/layout-composition.json +52 -0
- package/examples/evals/lint-overlay.json +117 -0
- package/examples/evals/microcopy.json +52 -0
- package/examples/evals/observability-modeling.json +52 -0
- package/examples/evals/pattern-recognition.json +96 -0
- package/examples/evals/performance-engineering.json +52 -0
- package/examples/evals/refactor.json +128 -0
- package/examples/evals/semiotics.json +52 -0
- package/examples/evals/skill-infrastructure.json +96 -0
- package/examples/evals/skill-router.json +140 -0
- package/examples/evals/skill-router.routing.json +113 -0
- package/examples/evals/system-interface-contracts.json +52 -0
- package/examples/evals/task-analysis.json +52 -0
- package/examples/evals/testing-strategy.json +118 -0
- package/examples/evals/type-safety.json +249 -0
- package/examples/evals/visual-design-foundations.json +52 -0
- package/examples/evals/webhook-integration.json +52 -0
- package/examples/exports/a11y.skill-md.md +80 -0
- package/examples/exports/debugging.skill-md.md +80 -0
- package/examples/exports/refactor.skill-md.md +78 -0
- package/examples/exports/testing-strategy.skill-md.md +81 -0
- package/examples/projects/markdown-static-site/README.md +115 -0
- package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
- package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
- package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
- package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
- package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
- package/examples/projects/saas-stripe-postgres/README.md +208 -0
- package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
- package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
- package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
- package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
- package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
- package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
- package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
- package/examples/protocol/skill-metadata-template.md +301 -0
- package/examples/protocol/skills.manifest.sample.json +13245 -0
- package/examples/skill-metadata-template.md +317 -0
- package/examples/skills.manifest.sample.json +13519 -0
- package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
- package/marketplace/README.md +17 -0
- package/marketplace/skills/a11y/SKILL.md +66 -0
- package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
- package/marketplace/skills/agent-engineering/SKILL.md +386 -0
- package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
- package/marketplace/skills/ai-native-development/SKILL.md +294 -0
- package/marketplace/skills/api-design/SKILL.md +60 -0
- package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
- package/marketplace/skills/background-jobs/SKILL.md +265 -0
- package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
- package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
- package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
- package/marketplace/skills/code-review/SKILL.md +120 -0
- package/marketplace/skills/color-system-design/SKILL.md +43 -0
- package/marketplace/skills/component-architecture/SKILL.md +126 -0
- package/marketplace/skills/compression/SKILL.md +112 -0
- package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
- package/marketplace/skills/connection-pooling/SKILL.md +105 -0
- package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
- package/marketplace/skills/content-monitor/SKILL.md +209 -0
- package/marketplace/skills/context-engineering/SKILL.md +320 -0
- package/marketplace/skills/context-graph/SKILL.md +174 -0
- package/marketplace/skills/context-management/SKILL.md +174 -0
- package/marketplace/skills/context-window/SKILL.md +239 -0
- package/marketplace/skills/contract-testing/SKILL.md +120 -0
- package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
- package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
- package/marketplace/skills/data-modeling/SKILL.md +59 -0
- package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
- package/marketplace/skills/database-migration/SKILL.md +429 -0
- package/marketplace/skills/debugging/SKILL.md +67 -0
- package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
- package/marketplace/skills/design-module-composition/SKILL.md +43 -0
- package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
- package/marketplace/skills/design-thinking/SKILL.md +44 -0
- package/marketplace/skills/diagnosis/SKILL.md +296 -0
- package/marketplace/skills/diff-analysis/SKILL.md +188 -0
- package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
- package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
- package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
- package/marketplace/skills/error-boundary/SKILL.md +235 -0
- package/marketplace/skills/error-tracking/SKILL.md +261 -0
- package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
- package/marketplace/skills/evaluation/SKILL.md +113 -0
- package/marketplace/skills/event-contract-design/SKILL.md +60 -0
- package/marketplace/skills/event-storming/SKILL.md +56 -0
- package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
- package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
- package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
- package/marketplace/skills/generative-ui/SKILL.md +118 -0
- package/marketplace/skills/graph-audit/SKILL.md +81 -0
- package/marketplace/skills/guardrails/SKILL.md +118 -0
- package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
- package/marketplace/skills/http-semantics/SKILL.md +136 -0
- package/marketplace/skills/ideation/SKILL.md +41 -0
- package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
- package/marketplace/skills/information-architecture/SKILL.md +59 -0
- package/marketplace/skills/integration-test-design/SKILL.md +111 -0
- package/marketplace/skills/intent-recognition/SKILL.md +136 -0
- package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
- package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
- package/marketplace/skills/journey-mapping/SKILL.md +41 -0
- package/marketplace/skills/keywords/SKILL.md +213 -0
- package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
- package/marketplace/skills/layout-composition/SKILL.md +59 -0
- package/marketplace/skills/linguistics/SKILL.md +429 -0
- package/marketplace/skills/lint-overlay/SKILL.md +76 -0
- package/marketplace/skills/mental-models/SKILL.md +126 -0
- package/marketplace/skills/merge-queue/SKILL.md +94 -0
- package/marketplace/skills/methodology/SKILL.md +317 -0
- package/marketplace/skills/microcopy/SKILL.md +232 -0
- package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
- package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
- package/marketplace/skills/mutation-testing/SKILL.md +112 -0
- package/marketplace/skills/naming-conventions/SKILL.md +112 -0
- package/marketplace/skills/observability-modeling/SKILL.md +59 -0
- package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
- package/marketplace/skills/owasp-security/SKILL.md +153 -0
- package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
- package/marketplace/skills/performance-budgets/SKILL.md +185 -0
- package/marketplace/skills/performance-engineering/SKILL.md +58 -0
- package/marketplace/skills/performance-testing/SKILL.md +125 -0
- package/marketplace/skills/printify/SKILL.md +42 -0
- package/marketplace/skills/prioritization/SKILL.md +118 -0
- package/marketplace/skills/problem-framing/SKILL.md +41 -0
- package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
- package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
- package/marketplace/skills/prompt-craft/SKILL.md +134 -0
- package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
- package/marketplace/skills/property-based-testing/SKILL.md +100 -0
- package/marketplace/skills/prototyping/SKILL.md +43 -0
- package/marketplace/skills/query-optimization/SKILL.md +144 -0
- package/marketplace/skills/real-time-updates/SKILL.md +324 -0
- package/marketplace/skills/ref-patterns/SKILL.md +284 -0
- package/marketplace/skills/refactor/SKILL.md +65 -0
- package/marketplace/skills/rendering-models/SKILL.md +142 -0
- package/marketplace/skills/replication-patterns/SKILL.md +110 -0
- package/marketplace/skills/research-synthesis/SKILL.md +41 -0
- package/marketplace/skills/route-handler-design/SKILL.md +347 -0
- package/marketplace/skills/schema-evolution/SKILL.md +140 -0
- package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
- package/marketplace/skills/semantic-center/SKILL.md +194 -0
- package/marketplace/skills/semantic-relations/SKILL.md +250 -0
- package/marketplace/skills/semantics/SKILL.md +366 -0
- package/marketplace/skills/semiotics/SKILL.md +230 -0
- package/marketplace/skills/seo-strategy/SKILL.md +260 -0
- package/marketplace/skills/server-actions-design/SKILL.md +243 -0
- package/marketplace/skills/server-components-design/SKILL.md +190 -0
- package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
- package/marketplace/skills/shopify/SKILL.md +42 -0
- package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
- package/marketplace/skills/skill-router/SKILL.md +71 -0
- package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
- package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
- package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
- package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
- package/marketplace/skills/state-management/SKILL.md +134 -0
- package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
- package/marketplace/skills/summarization/SKILL.md +156 -0
- package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
- package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
- package/marketplace/skills/task-analysis/SKILL.md +201 -0
- package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
- package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
- package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
- package/marketplace/skills/test-driven-development/SKILL.md +96 -0
- package/marketplace/skills/testing-strategy/SKILL.md +67 -0
- package/marketplace/skills/theme-system-design/SKILL.md +43 -0
- package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
- package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
- package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
- package/marketplace/skills/type-safety/SKILL.md +177 -0
- package/marketplace/skills/typography-system/SKILL.md +43 -0
- package/marketplace/skills/usability-testing/SKILL.md +43 -0
- package/marketplace/skills/user-research/SKILL.md +43 -0
- package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
- package/marketplace/skills/version-control/SKILL.md +233 -0
- package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
- package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
- package/marketplace/skills/webhook-integration/SKILL.md +331 -0
- package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
- package/package.json +67 -0
- package/schemas/manifest.schema.json +811 -0
- package/schemas/manifest.v2.schema.json +164 -0
- package/schemas/manifest.v3.schema.json +758 -0
- package/schemas/manifest.v4.schema.json +755 -0
- package/schemas/manifest.v5.schema.json +755 -0
- package/schemas/manifest.v6.schema.json +811 -0
- package/schemas/skill.context.jsonld +279 -0
- package/schemas/skill.schema.json +919 -0
- package/schemas/skill.v2.schema.json +201 -0
- package/schemas/skill.v3.schema.json +827 -0
- package/schemas/skill.v4.schema.json +822 -0
- package/schemas/skill.v5.schema.json +830 -0
- package/schemas/skill.v6.schema.json +946 -0
- package/schemas/vocabulary/keywords.json +180 -0
- package/schemas/vocabulary/workspace_tags.json +23 -0
- package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
- package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
- package/scripts/__tests__/test-export-parser-drift.js +149 -0
- package/scripts/__tests__/test-marketplace-export.js +114 -0
- package/scripts/__tests__/test-router-paths.js +82 -0
- package/scripts/__tests__/test-stability-promotion.js +244 -0
- package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
- package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
- package/scripts/backfill-schema-version.js +198 -0
- package/scripts/build-field-reference.js +160 -0
- package/scripts/build-retrieval-baseline.js +511 -0
- package/scripts/check-markdown-links.js +211 -0
- package/scripts/check-protocol-consistency.js +979 -0
- package/scripts/export-marketplace-skills.js +610 -0
- package/scripts/export-skill.js +374 -0
- package/scripts/generate-manifest.js +787 -0
- package/scripts/lib/alias-contract.js +83 -0
- package/scripts/lib/audit-prompt-builder.js +771 -0
- package/scripts/lib/mock-grader.js +134 -0
- package/scripts/lib/parse-frontmatter.js +429 -0
- package/scripts/lib/roots.js +119 -0
- package/scripts/lint/check-archetype-sections.js +185 -0
- package/scripts/lint/check-category-enum.js +83 -0
- package/scripts/lint/check-routing-eval.js +146 -0
- package/scripts/lint/check-routing-quality.js +211 -0
- package/scripts/lint/check-stability-promotion.js +220 -0
- package/scripts/lint/format-code-frame.js +206 -0
- package/scripts/marketplace-install.js +125 -0
- package/scripts/migrate-category-to-enum.js +169 -0
- package/scripts/migrate-skill-v2-to-v3.js +424 -0
- package/scripts/migrate-skill-v3-to-v4.js +200 -0
- package/scripts/migrate-skill-v5-to-v6.js +304 -0
- package/scripts/restructure-by-category.js +85 -0
- package/scripts/seed-publication-classification.js +282 -0
- package/scripts/skill-audit.js +893 -0
- package/scripts/skill-graph-drift.js +483 -0
- package/scripts/skill-graph-route.js +766 -0
- package/scripts/skill-graph-routing-eval.js +393 -0
- package/scripts/skill-lint.js +1317 -0
- package/scripts/skill-overlap.js +213 -0
- package/scripts/verify-skill-md-export.js +201 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microcopy
|
|
3
|
+
description: "Use when writing or reviewing functional UI text: button labels, empty states, tooltips, dialogs, placeholders, loading/progress messages, toasts, inline validation, permission copy, or onboarding steps. Covers interface-copy patterns such as verb-first action labels, acknowledge-explain-guide empty states, one-sentence tooltips, consequence-first confirmations, progressive loading language, and blur/fix validation messages. Do NOT use for marketing persuasion, documentation prose/guide structure, feedback-state staging, or general linguistic rationale behind wording."
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: "Stack-agnostic UX-writing patterns. The button-label, empty-state, tooltip, dialog, loading, validation, and toast rules apply to any web, mobile, or desktop UI; example product copy uses generic e-commerce framings (storefront, fulfillment partner, orders) — substitute the equivalents from your own product domain."
|
|
6
|
+
allowed-tools: Read Grep
|
|
7
|
+
metadata:
|
|
8
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"design\",\"domain\":\"design/ux\",\"scope\":\"portable\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-06\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-06\\\\\\\"}\",\"eval_artifacts\":\"present\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"button label microcopy\\\\\\\",\\\\\\\"empty-state copy\\\\\\\",\\\\\\\"tooltip text rule\\\\\\\",\\\\\\\"confirmation dialog wording\\\\\\\",\\\\\\\"inline validation message\\\\\\\",\\\\\\\"toast notification copy\\\\\\\",\\\\\\\"placeholder text rule\\\\\\\",\\\\\\\"loading-state messaging\\\\\\\",\\\\\\\"functional UI text\\\\\\\",\\\\\\\"ux writing patterns\\\\\\\",\\\\\\\"destructive-action confirmation copy\\\\\\\",\\\\\\\"permission request copy\\\\\\\",\\\\\\\"onboarding step copy\\\\\\\",\\\\\\\"microcopy verb-first button\\\\\\\",\\\\\\\"acknowledge-explain-guide empty state\\\\\\\",\\\\\\\"blame-free toast wording\\\\\\\"]\",\"examples\":\"[\\\\\\\"rewrite this button label so it names the actual action instead of saying Submit\\\\\\\",\\\\\\\"what should the empty state say when a user has no orders yet?\\\\\\\",\\\\\\\"draft tooltip text for a production-cost field that explains what it means in one sentence\\\\\\\",\\\\\\\"this destructive confirmation dialog says OK — what should the button label be instead?\\\\\\\",\\\\\\\"the inline validation message says Invalid input — make it specific and actionable\\\\\\\",\\\\\\\"draft a toast message for a successful order export with undo\\\\\\\",\\\\\\\"what should the loading state say when a sync takes longer than 10 seconds?\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"write the marketing headline for the pricing page\\\\\\\",\\\\\\\"review this WCAG 2.2 contrast violation on the dashboard\\\\\\\",\\\\\\\"explain the morphology rule behind verb-first function names\\\\\\\",\\\\\\\"restructure this help-center article into a tutorial\\\\\\\",\\\\\\\"decide the kebab-case format for this new CSS class\\\\\\\",\\\\\\\"rename this React component across all call-sites\\\\\\\"]\",\"relations\":\"{\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"linguistics\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"linguistics owns the underlying language rules (morphology, polysemy, audience register, blame-free framing as a general principle); microcopy owns the specific UI-text patterns where those rules apply (button label rule, empty-state structure, tooltip rule, confirmation-dialog rule) — the same 'rewrite this UI text' prompt routes by whether the user wants the linguistic rationale or the concrete UX-writing pattern\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"a11y\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"a11y owns the accessibility contracts that govern how copy is announced (aria-live regions, aria-label fallbacks, screen-reader semantics); microcopy owns the words themselves — the same 'fix this UI text' prompt routes by whether the trigger is accessibility compliance or copy quality\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"interaction-feedback\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"interaction-feedback owns when, where, and how feedback states appear; microcopy owns the words inside those states\\\\\\\"}],\\\\\\\"related\\\\\\\":[\\\\\\\"linguistics\\\\\\\",\\\\\\\"task-analysis\\\\\\\",\\\\\\\"intent-recognition\\\\\\\",\\\\\\\"interaction-feedback\\\\\\\",\\\\\\\"form-ux-architecture\\\\\\\"],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"linguistics\\\\\\\",\\\\\\\"a11y\\\\\\\"]}\",\"portability\":\"{\\\\\\\"readiness\\\\\\\":\\\\\\\"scripted\\\\\\\",\\\\\\\"targets\\\\\\\":[\\\\\\\"skill-md\\\\\\\"]}\",\"lifecycle\":\"{\\\\\\\"stale_after_days\\\\\\\":365,\\\\\\\"review_cadence\\\\\\\":\\\\\\\"quarterly\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/microcopy/SKILL.md\"}"
|
|
9
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
10
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
11
|
+
skill_graph_project: Skill Graph
|
|
12
|
+
skill_graph_canonical_skill: skills/microcopy/SKILL.md
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Microcopy
|
|
16
|
+
|
|
17
|
+
## Coverage
|
|
18
|
+
|
|
19
|
+
Functional UI text patterns across all interactive surfaces:
|
|
20
|
+
|
|
21
|
+
- **Button labels** — verb-first, specific action, max 3 words; never generic ("Submit", "OK", "Yes", "Continue")
|
|
22
|
+
- **Empty states** — three-part structure: acknowledge → explain → guide, with one primary action
|
|
23
|
+
- **Tooltips** — one sentence, no terminal period, answers "what is this?"
|
|
24
|
+
- **Confirmation dialogs** — state the consequence first, name the action in the button, always provide an escape
|
|
25
|
+
- **Placeholder text** — example format not instruction; never the only label for a field
|
|
26
|
+
- **Loading and progress messages** — progressive disclosure: nothing → spinner → skeleton → message → reassurance, by elapsed time
|
|
27
|
+
- **Error / success / warning messages** — three-part What → Why → What-to-do structure with blame-free framing
|
|
28
|
+
- **Inline form-validation messages** — appear on blur, disappear on fix, specific not generic
|
|
29
|
+
- **Toast / snackbar messages** — action plus context, undo for reversible actions, auto-dismiss after 5 seconds, max 2 lines
|
|
30
|
+
- **Permission request copy** — explain *why* before asking
|
|
31
|
+
- **Onboarding step copy** — one action per step, progressive disclosure, time-honest
|
|
32
|
+
|
|
33
|
+
## Philosophy
|
|
34
|
+
|
|
35
|
+
Microcopy is the most-read, least-reviewed text in any application. A user may never read the docs, skip the onboarding, and ignore the marketing — but they will read the button label before clicking it. They will read the error message when something fails. They will read the empty state when they first arrive. These micro-moments determine whether the user feels confident or confused, and they compound across every interaction.
|
|
36
|
+
|
|
37
|
+
The failure mode is predictable: developers write placeholder microcopy during implementation ("Click here", "Error occurred", "No data"), it ships because nobody reviews it, and it stays forever. Agents make it worse — they default to verbose, hedged, generic text ("An error has occurred while processing your request. Please try again later.") when users need short, specific, actionable text ("Payment failed — check your card number.").
|
|
38
|
+
|
|
39
|
+
This skill exists because microcopy quality is structurally unowned in most projects. Marketing copy has a copywriter. Documentation has a tech writer. Naming has a convention. But nobody owns the words *inside* the working interface — the button that says "Submit" when it should say "Save Changes", the empty state that says "No items" when it should say "No orders yet — connect your storefront to start syncing." The cost of that gap is paid one click at a time.
|
|
40
|
+
|
|
41
|
+
> **Scope boundary:** microcopy writes FUNCTIONAL UI text — button labels, error messages, empty states, tooltips, confirmation dialogs. It does NOT cover marketing copy or content-strategy decisions; those belong to dedicated copywriting and content-strategy skills.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Button Labels
|
|
46
|
+
|
|
47
|
+
Buttons are the primary action interface. Every button label is a micro-contract: it promises what will happen when clicked.
|
|
48
|
+
|
|
49
|
+
**Rules:**
|
|
50
|
+
|
|
51
|
+
1. **Verb-first**: "Save Changes", "Connect Storefront", "Export CSV" — not "Changes", "Storefront", "CSV"
|
|
52
|
+
2. **Specific action**: "Delete Order" not "Delete", "Send Invitation" not "Send"
|
|
53
|
+
3. **Max 3 words** for primary actions: "Save", "Save Changes", "Save and Close"
|
|
54
|
+
4. **Match the consequence**: if clicking deletes data, the button says "Delete" not "OK". If it sends an email, it says "Send Email" not "Confirm"
|
|
55
|
+
5. **Avoid generic labels**: "Submit", "OK", "Yes", "Continue" are almost always wrong — name the actual action
|
|
56
|
+
6. **Cancel is always available**: destructive dialogs need both the action ("Delete Order") and the escape ("Cancel")
|
|
57
|
+
|
|
58
|
+
**Button label patterns by context:**
|
|
59
|
+
|
|
60
|
+
| Context | Bad | Good |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| Save form | Submit | Save Changes |
|
|
63
|
+
| Delete item | OK | Delete Order |
|
|
64
|
+
| Connect platform | Continue | Connect Storefront |
|
|
65
|
+
| Export data | Download | Export as CSV |
|
|
66
|
+
| Confirm send | Yes | Send Invitation |
|
|
67
|
+
| Dismiss dialog | Close | Cancel |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 2. Empty States
|
|
72
|
+
|
|
73
|
+
Empty states are the first impression for every new feature. They must acknowledge, explain, and guide.
|
|
74
|
+
|
|
75
|
+
**Three-part structure:**
|
|
76
|
+
|
|
77
|
+
1. **Acknowledge** — "No orders yet" — confirm the user is in the right place and the emptiness is expected
|
|
78
|
+
2. **Explain** — "Orders will appear here once your storefront syncs" — tell them why it's empty and when it won't be
|
|
79
|
+
3. **Guide** — "Connect Storefront" (button) — give them the action that fills the empty state
|
|
80
|
+
|
|
81
|
+
**Rules:**
|
|
82
|
+
|
|
83
|
+
- Never just "No data" or "Nothing to show" — this is a dead end
|
|
84
|
+
- Use "yet" to imply future content: "No orders yet" vs "No orders"
|
|
85
|
+
- Include one primary action button that resolves the empty state
|
|
86
|
+
- For filtered empty states: "No orders match your filters" + "Clear filters" button
|
|
87
|
+
- For error empty states: "Could not load orders" + "Try again" button + brief explanation
|
|
88
|
+
|
|
89
|
+
**Common empty-state patterns:**
|
|
90
|
+
|
|
91
|
+
| Surface | Message | Action |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| Orders table (new user) | No orders yet | Connect Storefront |
|
|
94
|
+
| Orders table (filtered) | No orders match these filters | Clear filters |
|
|
95
|
+
| Dashboard (no data) | Connect a storefront to see your profits | Get Started |
|
|
96
|
+
| Product list (empty) | No products synced yet | Sync Products |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3. Tooltips
|
|
101
|
+
|
|
102
|
+
Tooltips answer one question: "What is this?"
|
|
103
|
+
|
|
104
|
+
**Rules:**
|
|
105
|
+
|
|
106
|
+
- One sentence maximum, no period at the end
|
|
107
|
+
- Answer "what is this?" or "why would I use this?" — not "how does this work?"
|
|
108
|
+
- Never repeat the label the tooltip is attached to
|
|
109
|
+
- No links in tooltips (they disappear on mouseout)
|
|
110
|
+
- Use sentence case for the first word only
|
|
111
|
+
- Appear on hover after 300ms delay, dismiss on mouseout
|
|
112
|
+
|
|
113
|
+
**Examples:**
|
|
114
|
+
|
|
115
|
+
- Production-cost field: "Cost of goods sold, including production and shipping to your fulfillment center"
|
|
116
|
+
- Confidence badge: "How complete the profit calculation is for this order"
|
|
117
|
+
- Sync status icon: "Last synced 5 minutes ago from your storefront"
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. Confirmation Dialogs
|
|
122
|
+
|
|
123
|
+
Confirmation dialogs exist for one reason: preventing irreversible mistakes.
|
|
124
|
+
|
|
125
|
+
**Rules:**
|
|
126
|
+
|
|
127
|
+
1. **State the consequence first**: "This will permanently delete 3 orders and their associated profit data."
|
|
128
|
+
2. **Name the action in the button**: "Delete 3 Orders" not "Confirm" or "OK"
|
|
129
|
+
3. **Provide the escape**: "Cancel" button, always present
|
|
130
|
+
4. **No double negatives**: "Don't cancel" is never the right label
|
|
131
|
+
5. **Include the count**: "Delete 3 orders" not "Delete selected orders"
|
|
132
|
+
6. **Distinguish destructive from reversible**: red button + explicit "permanently" for destructive; normal button for reversible
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 5. Error Messages
|
|
137
|
+
|
|
138
|
+
Error messages are the most important microcopy in the application. When something fails, the user needs clarity, not apology.
|
|
139
|
+
|
|
140
|
+
**Three-part structure:**
|
|
141
|
+
|
|
142
|
+
1. **What happened**: "Payment failed" — state the failure clearly
|
|
143
|
+
2. **Why**: "Your card was declined" — give the specific reason if known
|
|
144
|
+
3. **What to do**: "Check your card details or try a different payment method" — actionable next step
|
|
145
|
+
|
|
146
|
+
**Rules:**
|
|
147
|
+
|
|
148
|
+
- Never "An error occurred" — say *what* errored
|
|
149
|
+
- Never "Please try again later" — say *what* to try or *when* later is
|
|
150
|
+
- Blame-free framing: "We couldn't sync your orders" not "You have a sync error"
|
|
151
|
+
- Include error codes only in technical contexts, never in user-facing messages
|
|
152
|
+
- For transient errors: "Sync paused — retrying automatically" with a progress indicator
|
|
153
|
+
|
|
154
|
+
The general blame-free / What → Why → What-to-do framing is the linguistic rule (see `linguistics`); microcopy applies it specifically to in-product error toasts, banners, and inline messages.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 6. Loading and Progress
|
|
159
|
+
|
|
160
|
+
Users tolerate waiting when they understand what's happening.
|
|
161
|
+
|
|
162
|
+
**Progressive disclosure by elapsed time:**
|
|
163
|
+
|
|
164
|
+
- **0 – 300 ms**: show nothing (the action feels instant)
|
|
165
|
+
- **300 ms – 2 s**: show spinner or skeleton (brief acknowledgment)
|
|
166
|
+
- **2 – 10 s**: show message ("Syncing your orders from your storefront…")
|
|
167
|
+
- **10 s+**: show progress ("Syncing orders… 47 of 312")
|
|
168
|
+
- **30 s+**: show reassurance ("This may take a few minutes for large stores. You can leave this page.")
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 7. Inline Validation Messages
|
|
173
|
+
|
|
174
|
+
Validation messages appear at the field level, not the form level.
|
|
175
|
+
|
|
176
|
+
**Rules:**
|
|
177
|
+
|
|
178
|
+
- Appear on blur (not on keystroke — that's hostile)
|
|
179
|
+
- Disappear as soon as the user fixes the input
|
|
180
|
+
- Specific, not generic: "Email must include @" not "Invalid input"
|
|
181
|
+
- Placed below the field, not in an alert box
|
|
182
|
+
- Red text + icon for errors, green for success (with non-color indicator for accessibility)
|
|
183
|
+
- Never use exclamation marks in validation messages
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 8. Toast / Snackbar Messages
|
|
188
|
+
|
|
189
|
+
Toasts confirm completed actions. They are the UI's "done" signal.
|
|
190
|
+
|
|
191
|
+
**Rules:**
|
|
192
|
+
|
|
193
|
+
- Action + context: "Order #1234 deleted" not "Item deleted"
|
|
194
|
+
- Include undo for reversible actions: "Order archived. Undo"
|
|
195
|
+
- Auto-dismiss after 5 seconds (configurable for actions with undo)
|
|
196
|
+
- Max 2 lines of text
|
|
197
|
+
- Stack from bottom, newest on top
|
|
198
|
+
- Never use toasts for errors — errors need persistent, in-context display
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Evals
|
|
203
|
+
|
|
204
|
+
This skill ships a comprehension-eval artifact at [`examples/evals/microcopy.json`](https://github.com/jacob-balslev/skill-graph/blob/main/examples/evals/microcopy.json). The checklist below is the authoring gate for functional UI text; the eval file is the grader surface.
|
|
205
|
+
|
|
206
|
+
## Verification
|
|
207
|
+
|
|
208
|
+
After writing or reviewing UI text, verify:
|
|
209
|
+
|
|
210
|
+
- [ ] Button labels are verb-first and name the actual action (no "Submit", "OK", "Yes", "Continue" alone)
|
|
211
|
+
- [ ] Empty states acknowledge → explain → guide, with a real next-step button
|
|
212
|
+
- [ ] Tooltips are one sentence, no terminal period, and answer "what is this?"
|
|
213
|
+
- [ ] Confirmation dialogs state the consequence first and name the action in the button (with a Cancel escape)
|
|
214
|
+
- [ ] Placeholder text is an example format, not the only label
|
|
215
|
+
- [ ] Loading messages follow progressive disclosure by elapsed time, not a single state
|
|
216
|
+
- [ ] Error messages follow What → Why → What-to-do, blame-free, with a specific action
|
|
217
|
+
- [ ] Inline validation appears on blur and disappears on fix; messages are specific, not generic
|
|
218
|
+
- [ ] Toasts include action + context and an undo path for reversible actions
|
|
219
|
+
- [ ] Functional UI text stays inside the interface — does not drift into marketing or documentation territory
|
|
220
|
+
|
|
221
|
+
## Do NOT Use When
|
|
222
|
+
|
|
223
|
+
| Instead, use | Why |
|
|
224
|
+
| --- | --- |
|
|
225
|
+
| `linguistics` | The user wants the underlying linguistic rule (morphology, polysemy, register), not the specific UI-text pattern. Linguistics owns the *why*; microcopy owns the *what to write*. |
|
|
226
|
+
| `documentation` | Writing or restructuring long-form prose for guides, tutorials, reference docs, or help-center articles. Documentation owns doc architecture and prose; microcopy owns in-product UI text. |
|
|
227
|
+
| `a11y` | Auditing UI text for screen-reader announcement, aria-live behavior, or color-contrast compliance. A11y owns the accessibility contracts; microcopy owns the words. |
|
|
228
|
+
| `naming-conventions` | Deciding the casing format for an artifact kind (kebab vs camel vs snake). Naming-conventions is for code identifiers, not user-facing UI strings. |
|
|
229
|
+
| `intent-recognition` | Disambiguating a user's intent from an ambiguous prompt. Intent-recognition is upstream of any UI; microcopy is the words the UI uses to respond. |
|
|
230
|
+
| (a copywriting skill) | Marketing headlines, pricing copy, landing-page persuasion, brand-voice work. Copywriting owns persuasive product surfaces; microcopy owns functional in-product text. |
|
|
231
|
+
| (a content-strategy skill) | Page structure, funnel strategy, or what content belongs on each page. Content strategy is upstream of microcopy. |
|
|
232
|
+
| `interaction-feedback` | When and how feedback states appear (timing, placement, persistence, recovery). Interaction-feedback owns the staging; microcopy owns the words inside the staged element. |
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: middleware-patterns
|
|
3
|
+
description: "Use when designing or reviewing Next.js middleware (`middleware.ts`): cross-cutting request/response transforms before route resolution, Edge Runtime constraints, `matcher` config, `NextRequest`/`NextResponse` API, the four response shapes (next/rewrite/redirect/direct), canonical patterns (auth gate, locale routing, A/B testing, header injection, geo-routing, bot blocking), and the design rule that middleware is for cross-cutting concerns across many routes — never per-route business logic. Do NOT use for per-route HTTP endpoint logic (use route-handler-design), Server Action mutations (use server-actions-design), abstract HTTP semantics (use http-semantics), CSP and hardening (use security-fundamentals), or the cross-cutting streaming model (use streaming-architecture)."
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Read Grep
|
|
6
|
+
metadata:
|
|
7
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/frontend\",\"scope\":\"reference\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-17\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-17\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"comprehension_state\":\"present\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"Next.js middleware\\\\\\\",\\\\\\\"middleware.ts file\\\\\\\",\\\\\\\"NextRequest NextResponse\\\\\\\",\\\\\\\"matcher config middleware\\\\\\\",\\\\\\\"Edge Runtime constraints\\\\\\\",\\\\\\\"NextResponse.redirect rewrite next\\\\\\\",\\\\\\\"auth check before route\\\\\\\",\\\\\\\"locale routing i18n middleware\\\\\\\",\\\\\\\"A/B testing variant rewrite\\\\\\\",\\\\\\\"CSP nonce middleware\\\\\\\",\\\\\\\"geo-routing X-Vercel-IP-Country\\\\\\\",\\\\\\\"request header injection\\\\\\\",\\\\\\\"bot blocking middleware\\\\\\\",\\\\\\\"middleware cookie set\\\\\\\"]\",\"triggers\":\"[\\\\\\\"how do I redirect unauthenticated users to login in Next.js\\\\\\\",\\\\\\\"how do I run code before every request in Next.js\\\\\\\",\\\\\\\"how do I set security headers globally in Next.js\\\\\\\",\\\\\\\"how do I do locale routing in App Router\\\\\\\",\\\\\\\"how do I do an A/B test with rewrites\\\\\\\",\\\\\\\"why does my middleware run on static assets\\\\\\\",\\\\\\\"can middleware do a database query\\\\\\\",\\\\\\\"how do I generate a CSP nonce per request\\\\\\\"]\",\"examples\":\"[\\\\\\\"design middleware that redirects unauthenticated users to /login while letting public routes through, configured via a matcher\\\\\\\",\\\\\\\"add a middleware that generates a per-request CSP nonce and injects it into both the request and response headers\\\\\\\",\\\\\\\"implement locale routing that detects Accept-Language and rewrites /about to /en/about for new visitors\\\\\\\",\\\\\\\"add bot blocking that returns 403 for known scraper user-agents while letting search-engine bots through\\\\\\\",\\\\\\\"tune a middleware that runs on every request down to 5ms so it stops adding latency to image fetches\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"implement a /api/posts POST endpoint (use route-handler-design)\\\\\\\",\\\\\\\"implement a delete-comment mutation triggered from a form button (use server-actions-design)\\\\\\\",\\\\\\\"explain what an HTTP 308 means vs 307 (use http-semantics)\\\\\\\",\\\\\\\"design the full CSP policy and the rest of the security-header strategy (use security-fundamentals)\\\\\\\",\\\\\\\"design a long-lived SSE stream from middleware (use streaming-architecture)\\\\\\\",\\\\\\\"design the CSP policy, threat model, or OWASP audit for a system (use security-fundamentals)\\\\\\\",\\\\\\\"decide what an HTTP method, status code, or header should mean per RFC 9110 (use http-semantics)\\\\\\\",\\\\\\\"design signature verification, idempotency, or retry semantics for vendor webhooks (use webhook-integration)\\\\\\\"]\",\"relations\":\"{\\\\\\\"related\\\\\\\":[\\\\\\\"route-handler-design\\\\\\\",\\\\\\\"server-actions-design\\\\\\\",\\\\\\\"http-semantics\\\\\\\",\\\\\\\"security-fundamentals\\\\\\\",\\\\\\\"server-components-design\\\\\\\",\\\\\\\"client-server-boundary\\\\\\\",\\\\\\\"webhook-integration\\\\\\\"],\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"route-handler-design\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"middleware runs once before route resolution and applies to many routes via a matcher; route-handler-design runs for one route and one method after route resolution. Middleware owns the cross-cutting layer (auth gate, locale rewrite, header injection); route handlers own the per-route logic. They compose: middleware passes through to the handler, the handler executes, the response flows back.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"server-actions-design\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"server-actions-design owns the internal-mutation surface invoked from the app's own UI; middleware is the cross-cutting request preprocessor that runs before any route or action. A Server Action call passes through middleware on its way to the server.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"server-components-design\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"server-components-design owns the render path that produces a page; middleware runs upstream of render and can rewrite, redirect, or pass through. Middleware does not replace render; it gates and rewrites it.\\\\\\\"}],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"code-review\\\\\\\",\\\\\\\"security-fundamentals\\\\\\\"]}\",\"mental_model\":\"|\",\"purpose\":\"|\",\"boundary\":\"|\",\"analogy\":\"Middleware is to a Next.js app what a building's lobby concierge is to its offices — every visitor passes through the lobby before reaching any specific floor; the concierge can check ID badges (auth gate), redirect visitors to the right elevator (locale or A/B rewrite), hand out lanyards with security policies attached (CSP nonce, request-ID header), or turn away bad-faith visitors at the door (bot block). The concierge cannot do the work of any specific office (per-route business logic) — that happens after the visitor gets off the elevator — but they enforce the rules that apply to every floor.\",\"misconception\":\"|\",\"concept\":\"{\\\\\\\"definition\\\\\\\":\\\\\\\"Next.js middleware is a single async function exported as default from `middleware.ts` at the project root that runs on the Edge Runtime before route resolution for every request matching its `matcher` config. It receives a `NextRequest` and returns a `NextResponse` (or implicitly `NextResponse.next()`), and can do four things: pass through (`next`), rewrite to a different internal path (`rewrite`), redirect to a different URL (`redirect`), or short-circuit with a direct response. It runs once per request before any page render, Server Component fetch, Server Action, or Route Handler executes — making it the only place to apply genuinely cross-cutting concerns without per-route ceremony.\\\\\\\",\\\\\\\"mental_model\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"purpose\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"boundary\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"taxonomy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"analogy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"misconception\\\\\\\":\\\\\\\"|\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/middleware-patterns/SKILL.md\"}"
|
|
8
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
9
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
10
|
+
skill_graph_project: Skill Graph
|
|
11
|
+
skill_graph_canonical_skill: skills/middleware-patterns/SKILL.md
|
|
12
|
+
skill_graph_export_description: shortened for Agent Skills 1024-character description limit; canonical source keeps the full routing contract
|
|
13
|
+
skill_graph_canonical_description_length: "1178"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Middleware Patterns
|
|
17
|
+
|
|
18
|
+
## Coverage
|
|
19
|
+
|
|
20
|
+
The discipline of designing Next.js middleware: the one-file-per-project contract (`middleware.ts` at the root or under `src/`, single default export), the Edge Runtime constraints that govern what code can and cannot run there, the `matcher` config that filters which paths trigger middleware, the `NextRequest` / `NextResponse` API surface (cookies, geo, IP, headers), the four response shapes (`next`, `rewrite`, `redirect`, direct response), the canonical pattern library (authentication gate, locale routing, A/B testing, security header injection, geo-routing, bot blocking, request-id correlation), the performance discipline that every matched request pays the cost, and the central design rule: middleware is for cross-cutting concerns that apply across many routes — never for per-route business logic.
|
|
21
|
+
|
|
22
|
+
## Philosophy
|
|
23
|
+
|
|
24
|
+
The Pages Router's request lifecycle was: server hits `getServerSideProps`, which returns props, which render the page. The App Router added more layers (Server Components, Server Actions, Route Handlers), but kept one thing constant — they all run *after* the route is resolved.
|
|
25
|
+
|
|
26
|
+
Middleware runs *before*. It is the only layer where you can intercept a request without knowing which route it will eventually hit. That makes it the right home for concerns that apply across the entire app or large subsets of it: "every request needs an auth check", "every request needs a request-id header", "every request to `/admin/*` needs a role check", "every page needs a CSP nonce".
|
|
27
|
+
|
|
28
|
+
The architectural trade is **breadth for power**. Middleware:
|
|
29
|
+
|
|
30
|
+
- Runs on the Edge Runtime — limited APIs, no Node-specific dependencies, no large packages.
|
|
31
|
+
- Runs on every matched request — performance ceiling matters because the cost multiplies.
|
|
32
|
+
- Has no per-route knowledge until the rewrite/redirect resolves — cannot read route-specific params or query the database for that route's data.
|
|
33
|
+
- Cannot read the response body — it sits in front of the response, not over it.
|
|
34
|
+
|
|
35
|
+
In exchange, it can shape the entire request/response edge in a single place. Done well, it removes ceremony from every route; done badly, it adds latency to every request and concentrates business logic in a file that's hard to test.
|
|
36
|
+
|
|
37
|
+
**The discipline of middleware is to keep it small, fast, and cross-cutting.** When a piece of logic only applies to one route, it does not belong here. When it requires a database lookup that adds 50ms, it does not belong here. When the code is hard to reason about, it definitely does not belong here.
|
|
38
|
+
|
|
39
|
+
## The File Contract
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// middleware.ts (project root, or src/ if using src layout)
|
|
43
|
+
import { NextResponse } from 'next/server'
|
|
44
|
+
import type { NextRequest } from 'next/server'
|
|
45
|
+
|
|
46
|
+
export async function middleware(request: NextRequest) {
|
|
47
|
+
// ... transform or gate the request ...
|
|
48
|
+
return NextResponse.next()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const config = {
|
|
52
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **One file per project.** There is no chain of middleware files. Compose multiple concerns inside a single `middleware` function.
|
|
57
|
+
- **Default export.** The named export pattern (`export async function middleware`) and the default export both work; pick one.
|
|
58
|
+
- **`config.matcher`** filters which paths trigger middleware. **Critical**: the default — no matcher — runs on every single request including static assets. Always set a matcher.
|
|
59
|
+
|
|
60
|
+
### Matcher syntax
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
export const config = {
|
|
64
|
+
matcher: [
|
|
65
|
+
'/dashboard/:path*', // glob
|
|
66
|
+
'/((?!api|_next/static|_next/image|favicon.ico).*)', // negative lookahead — everything except these
|
|
67
|
+
{
|
|
68
|
+
source: '/api/admin/:path*',
|
|
69
|
+
missing: [{ type: 'header', key: 'next-action' }], // exclude Server Action calls
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Matchers compile to regular expressions at build time. They cannot use runtime values. Complex negative lookaheads are common because the default-matches-everything behavior is rarely what you want — image fetches, static assets, prefetch requests, and webhook routes should usually be excluded.
|
|
76
|
+
|
|
77
|
+
The biggest middleware footgun is forgetting to exclude `/_next/static` and `/_next/image`, which makes every image fetch run middleware code on the hot path.
|
|
78
|
+
|
|
79
|
+
## The Four Response Shapes
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { NextResponse } from 'next/server'
|
|
83
|
+
|
|
84
|
+
// 1. Pass through — let the request continue to its route
|
|
85
|
+
return NextResponse.next()
|
|
86
|
+
|
|
87
|
+
// 2. Rewrite — internally route to a different path; URL bar unchanged
|
|
88
|
+
return NextResponse.rewrite(new URL('/en/about', request.url))
|
|
89
|
+
|
|
90
|
+
// 3. Redirect — send a 30x to the browser; URL bar changes
|
|
91
|
+
return NextResponse.redirect(new URL('/login', request.url))
|
|
92
|
+
|
|
93
|
+
// 4. Direct response — short-circuit; return a response without hitting any route
|
|
94
|
+
return new NextResponse('Forbidden', { status: 403 })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Choose based on what the user should see and what should change:
|
|
98
|
+
|
|
99
|
+
| Goal | Use |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Continue to the originally requested route, possibly with modified headers/cookies | `next()` (often with `.headers.set()` on the response) |
|
|
102
|
+
| Serve a different route's content under the same URL (A/B test, locale variant, feature flag) | `rewrite` |
|
|
103
|
+
| Send the user to a different URL (login redirect, canonical redirect, locale-detection redirect) | `redirect` |
|
|
104
|
+
| Block the request entirely (rate limit hit, bot blocked, missing auth on protected API) | direct response with appropriate status |
|
|
105
|
+
|
|
106
|
+
**Rewrite vs redirect** is a load-bearing distinction: rewrites are invisible to the user (the URL stays the same), redirects are visible (the URL changes and the browser does a second request). If you want the user to see they've been moved (`/old-path` → `/new-path`), redirect. If you want to keep their URL and serve different content (A/B variant, internal locale path), rewrite.
|
|
107
|
+
|
|
108
|
+
## Edge Runtime Constraints
|
|
109
|
+
|
|
110
|
+
Middleware runs on Edge. Things to know:
|
|
111
|
+
|
|
112
|
+
| Capability | Available |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `fetch` | ✅ |
|
|
115
|
+
| Web Crypto (`crypto.subtle`, `crypto.randomUUID`) | ✅ |
|
|
116
|
+
| Web Streams (`ReadableStream`, `TransformStream`) | ✅ |
|
|
117
|
+
| `URL`, `URLSearchParams`, `Request`, `Response` | ✅ |
|
|
118
|
+
| `setTimeout` / `setInterval` | ⚠️ best-effort, may not fire after response |
|
|
119
|
+
| Node `crypto` module | ❌ |
|
|
120
|
+
| Node `fs`, `child_process`, `net`, `dns` | ❌ |
|
|
121
|
+
| Most npm packages that aren't pure JS | ❌ |
|
|
122
|
+
| Large bundle sizes | ❌ (Vercel: ~1MB ceiling on middleware code) |
|
|
123
|
+
|
|
124
|
+
Middleware code is bundled and shipped to Edge nodes globally. Cold-start is fast (~10–50ms) but the trade is a tight capability surface. Anything you import — including transitive dependencies — must be Edge-compatible. A single `import` of a Node-only package breaks the build.
|
|
125
|
+
|
|
126
|
+
**Practical consequence**: don't reach for ORMs, full SDKs, or complex libraries in middleware. Hand-roll the small piece you need (decode a JWT, hash a token, parse a cookie). If the work genuinely needs Node — verifying a webhook signature with a vendor SDK, hitting a database — push it down into a Route Handler or Server Action instead.
|
|
127
|
+
|
|
128
|
+
## The Canonical Pattern Library
|
|
129
|
+
|
|
130
|
+
### 1. Authentication gate
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
export async function middleware(request: NextRequest) {
|
|
134
|
+
const session = request.cookies.get('session')?.value
|
|
135
|
+
const { pathname } = request.nextUrl
|
|
136
|
+
|
|
137
|
+
const isProtected = pathname.startsWith('/dashboard') || pathname.startsWith('/admin')
|
|
138
|
+
if (isProtected && !session) {
|
|
139
|
+
const url = new URL('/login', request.url)
|
|
140
|
+
url.searchParams.set('redirectTo', pathname)
|
|
141
|
+
return NextResponse.redirect(url)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return NextResponse.next()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const config = {
|
|
148
|
+
matcher: ['/dashboard/:path*', '/admin/:path*'],
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The session cookie is *checked*, not *verified*. Cryptographic verification belongs inside the routes — middleware is for fast cookie presence checks. A signed-cookie verification that requires a JWT library is fine if the library is Edge-compatible; a database lookup to validate the session is not — that 50ms hits every protected page load.
|
|
153
|
+
|
|
154
|
+
### 2. Locale routing
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const LOCALES = ['en', 'es', 'fr', 'de']
|
|
158
|
+
const DEFAULT_LOCALE = 'en'
|
|
159
|
+
|
|
160
|
+
export async function middleware(request: NextRequest) {
|
|
161
|
+
const { pathname } = request.nextUrl
|
|
162
|
+
const hasLocale = LOCALES.some((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
|
|
163
|
+
if (hasLocale) return NextResponse.next()
|
|
164
|
+
|
|
165
|
+
const accept = request.headers.get('accept-language') ?? ''
|
|
166
|
+
const detected = LOCALES.find((l) => accept.includes(l)) ?? DEFAULT_LOCALE
|
|
167
|
+
|
|
168
|
+
return NextResponse.redirect(new URL(`/${detected}${pathname}`, request.url))
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
A first-visit user lands on `/about`, gets redirected to `/en/about`. Subsequent visits to locale-prefixed paths pass through. The redirect-once pattern keeps the URL canonical and lets the rest of the app assume locale is in the path.
|
|
173
|
+
|
|
174
|
+
### 3. A/B testing via rewrite
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
export async function middleware(request: NextRequest) {
|
|
178
|
+
if (request.nextUrl.pathname !== '/pricing') return NextResponse.next()
|
|
179
|
+
|
|
180
|
+
let variant = request.cookies.get('pricing-variant')?.value
|
|
181
|
+
if (!variant) {
|
|
182
|
+
variant = Math.random() < 0.5 ? 'a' : 'b'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const url = new URL(`/pricing-${variant}`, request.url)
|
|
186
|
+
const response = NextResponse.rewrite(url)
|
|
187
|
+
response.cookies.set('pricing-variant', variant, { maxAge: 60 * 60 * 24 * 30 })
|
|
188
|
+
return response
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The user sees `/pricing` in their URL bar but receives `/pricing-a` or `/pricing-b`. The cookie pins their variant so subsequent visits are consistent. The rewrite preserves the canonical URL for analytics and sharing.
|
|
193
|
+
|
|
194
|
+
### 4. Security header injection (with per-request CSP nonce)
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
export async function middleware(request: NextRequest) {
|
|
198
|
+
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
|
|
199
|
+
|
|
200
|
+
const csp = [
|
|
201
|
+
`default-src 'self'`,
|
|
202
|
+
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
|
|
203
|
+
`style-src 'self' 'nonce-${nonce}'`,
|
|
204
|
+
`img-src 'self' blob: data:`,
|
|
205
|
+
`font-src 'self'`,
|
|
206
|
+
`object-src 'none'`,
|
|
207
|
+
`base-uri 'self'`,
|
|
208
|
+
`form-action 'self'`,
|
|
209
|
+
`frame-ancestors 'none'`,
|
|
210
|
+
`upgrade-insecure-requests`,
|
|
211
|
+
].join('; ')
|
|
212
|
+
|
|
213
|
+
const requestHeaders = new Headers(request.headers)
|
|
214
|
+
requestHeaders.set('x-nonce', nonce)
|
|
215
|
+
requestHeaders.set('content-security-policy', csp)
|
|
216
|
+
|
|
217
|
+
const response = NextResponse.next({ request: { headers: requestHeaders } })
|
|
218
|
+
response.headers.set('content-security-policy', csp)
|
|
219
|
+
response.headers.set('x-content-type-options', 'nosniff')
|
|
220
|
+
response.headers.set('referrer-policy', 'strict-origin-when-cross-origin')
|
|
221
|
+
response.headers.set('permissions-policy', 'camera=(), microphone=(), geolocation=()')
|
|
222
|
+
return response
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The nonce flows to the request headers (so Server Components can read it via `headers()` and inject it into `<script>` tags) and to the response headers (so the browser enforces the CSP). The policy itself is just an example; the actual rules belong to the broader security strategy in `security-fundamentals`.
|
|
227
|
+
|
|
228
|
+
### 5. Geo-routing
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
export async function middleware(request: NextRequest) {
|
|
232
|
+
const country = request.geo?.country ?? 'US' // populated by Vercel
|
|
233
|
+
|
|
234
|
+
if (country === 'GB' && !request.nextUrl.pathname.startsWith('/uk')) {
|
|
235
|
+
return NextResponse.redirect(new URL(`/uk${request.nextUrl.pathname}`, request.url))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return NextResponse.next()
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`request.geo` is populated by Vercel from the request's IP-derived location. On other hosts it may be undefined — read it defensively.
|
|
243
|
+
|
|
244
|
+
### 6. Bot blocking
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const BLOCKED_AGENTS = [/AhrefsBot/i, /SemrushBot/i, /MJ12bot/i]
|
|
248
|
+
|
|
249
|
+
export async function middleware(request: NextRequest) {
|
|
250
|
+
const ua = request.headers.get('user-agent') ?? ''
|
|
251
|
+
if (BLOCKED_AGENTS.some((re) => re.test(ua))) {
|
|
252
|
+
return new NextResponse('Forbidden', { status: 403 })
|
|
253
|
+
}
|
|
254
|
+
return NextResponse.next()
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
UA strings are trivially spoofable — bot blocking via user-agent works for honest crawlers but does not stop adversaries. Use this for noise reduction, not security.
|
|
259
|
+
|
|
260
|
+
### 7. Request-ID correlation
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
export async function middleware(request: NextRequest) {
|
|
264
|
+
const requestId = request.headers.get('x-request-id') ?? crypto.randomUUID()
|
|
265
|
+
const requestHeaders = new Headers(request.headers)
|
|
266
|
+
requestHeaders.set('x-request-id', requestId)
|
|
267
|
+
|
|
268
|
+
const response = NextResponse.next({ request: { headers: requestHeaders } })
|
|
269
|
+
response.headers.set('x-request-id', requestId)
|
|
270
|
+
return response
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The request-id flows through to the route (readable via `headers()`) and back out to the client (visible in DevTools). Pair with structured logging that includes the id, and you get end-to-end traceability.
|
|
275
|
+
|
|
276
|
+
## Composing Multiple Concerns
|
|
277
|
+
|
|
278
|
+
There is one `middleware.ts`. Combine concerns inside it — typically in a clear order:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
export async function middleware(request: NextRequest) {
|
|
282
|
+
// 1. Block bots first — fast reject
|
|
283
|
+
const ua = request.headers.get('user-agent') ?? ''
|
|
284
|
+
if (BLOCKED_AGENTS.some((re) => re.test(ua))) {
|
|
285
|
+
return new NextResponse('Forbidden', { status: 403 })
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 2. Locale detection — redirect once for first-visit users
|
|
289
|
+
const localeRedirect = applyLocaleRouting(request)
|
|
290
|
+
if (localeRedirect) return localeRedirect
|
|
291
|
+
|
|
292
|
+
// 3. Auth gate — redirect to login for protected routes
|
|
293
|
+
const authRedirect = applyAuthGate(request)
|
|
294
|
+
if (authRedirect) return authRedirect
|
|
295
|
+
|
|
296
|
+
// 4. Pass through with security headers + request-id
|
|
297
|
+
return applyHeaders(request)
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Each helper returns either a short-circuit `NextResponse` or `null` (continue). The shape is a small chain of guards; the file stays readable. When the chain grows past ~5 concerns, it's a signal that the middleware is doing too much — push something down to per-route logic or to a separate request-time hook.
|
|
302
|
+
|
|
303
|
+
## Performance Discipline
|
|
304
|
+
|
|
305
|
+
Every matched request pays the middleware cost. Three rules:
|
|
306
|
+
|
|
307
|
+
1. **Tune the matcher.** If only `/dashboard/*` needs auth, match only `/dashboard/*`. Don't run auth checks against image fetches.
|
|
308
|
+
2. **Cap the time budget.** Target <10ms p99 for middleware execution. Slow middleware degrades every page on the site.
|
|
309
|
+
3. **No database calls.** A network round-trip in middleware is a tax on every request. Cache aggressively; use signed cookies that carry the data middleware needs without a lookup; defer DB checks to the route.
|
|
310
|
+
|
|
311
|
+
The performance budget is invisible until you load-test the site and see middleware dominate the latency profile. Build it in from the start.
|
|
312
|
+
|
|
313
|
+
## Common Anti-Patterns
|
|
314
|
+
|
|
315
|
+
| Anti-pattern | Why it's wrong | Fix |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| No matcher — middleware runs on `/_next/static`, `/_next/image`, `/favicon.ico` | Adds latency to every image fetch and static asset | Set a matcher with negative lookahead excluding `_next` paths and assets |
|
|
318
|
+
| Database query in middleware | Network round-trip on every request | Use signed cookies that carry the data, or push the lookup down to the route |
|
|
319
|
+
| Putting per-route business logic in middleware | Centralized file that hides the logic from the route that owns it | Move to the route; keep middleware for genuine cross-cutting |
|
|
320
|
+
| Importing a Node-only package | Edge build fails | Use Edge-compatible alternatives, or move the work to a Route Handler |
|
|
321
|
+
| Verifying a JWT signature against a remote JWKS endpoint without caching | Network call per request | Cache the JWKS in memory; or defer verification to the route |
|
|
322
|
+
| `redirect` when `rewrite` was meant (or vice versa) | URL changes when it shouldn't, or stays the same when it should | Choose based on whether the user should see the URL change |
|
|
323
|
+
| Forgetting to copy headers to the response when modifying request headers | Request-side changes invisible to client | Use `NextResponse.next({ request: { headers: ... } })` AND set the same on `response.headers` if the client needs to see them |
|
|
324
|
+
| Setting cookies on the request — middleware can't modify the request cookies the client sees | Cookie set silently lost | Set cookies on the response via `response.cookies.set(...)` |
|
|
325
|
+
| Running middleware on webhook routes that need raw body access | Middleware can consume the body or otherwise interfere with HMAC verification | Exclude webhook paths from the matcher |
|
|
326
|
+
| Single 100-line middleware doing 8 different things | Untestable, slow, hard to reason about | Decompose into named helpers; consider whether some concerns belong per-route |
|
|
327
|
+
|
|
328
|
+
## Verification
|
|
329
|
+
|
|
330
|
+
After applying this skill, verify:
|
|
331
|
+
|
|
332
|
+
- [ ] `config.matcher` is set and excludes `_next/static`, `_next/image`, `favicon.ico`, and any other paths that don't need middleware (typically webhooks).
|
|
333
|
+
- [ ] No database queries or other I/O that adds >10ms to the request happen inside middleware.
|
|
334
|
+
- [ ] All imported packages are Edge-Runtime-compatible (no Node `crypto`, `fs`, `child_process`, `net`).
|
|
335
|
+
- [ ] Cookies that need to reach the client are set on the response (`response.cookies.set`), not on the request.
|
|
336
|
+
- [ ] Modified request headers use `NextResponse.next({ request: { headers } })` so they flow to the route.
|
|
337
|
+
- [ ] The choice between `rewrite` and `redirect` matches whether the URL should visibly change.
|
|
338
|
+
- [ ] Webhook routes are excluded from the matcher to preserve raw-body access.
|
|
339
|
+
- [ ] Multiple concerns are decomposed into named helpers; one concern per helper.
|
|
340
|
+
- [ ] Auth checks are fast cookie/signature checks, not database lookups — deeper verification is deferred to the route.
|
|
341
|
+
- [ ] Security-header injection (if used) coordinates with the broader security strategy defined in `security-fundamentals`.
|
|
342
|
+
|
|
343
|
+
## Grounding Sources
|
|
344
|
+
|
|
345
|
+
- Next.js docs — [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware). The canonical reference for the `middleware.ts` convention.
|
|
346
|
+
- Next.js docs — [Matcher config](https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher). The path-filtering rules.
|
|
347
|
+
- Vercel docs — [Edge Runtime API reference](https://vercel.com/docs/functions/runtimes/edge-runtime). The capability surface middleware runs against.
|
|
348
|
+
- MDN — [Fetch API: Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). The Web-standard interface underlying `NextRequest`/`NextResponse`.
|
|
349
|
+
- RFC 9110 — [HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110). The protocol middleware operates on — methods, status codes, headers.
|
|
350
|
+
- OWASP — [Secure Headers Project](https://owasp.org/www-project-secure-headers/). The canonical reference for the security-header set middleware can inject.
|
|
351
|
+
- Vercel — [Building a strict CSP with nonces in Next.js](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy). The canonical per-request nonce pattern.
|
|
352
|
+
|
|
353
|
+
## Do NOT Use When
|
|
354
|
+
|
|
355
|
+
| Instead of this skill | Use | Why |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| Per-route HTTP endpoint logic — JSON APIs, webhook handlers, streaming responses | `route-handler-design` | Route Handlers own per-route per-method logic; middleware owns cross-cutting preprocessing. |
|
|
358
|
+
| Internal mutations triggered from this app's UI | `server-actions-design` | Server Actions are the in-app mutation surface; middleware sits upstream of them but does not replace them. |
|
|
359
|
+
| Understanding what each HTTP status or method means in the abstract | `http-semantics` | http-semantics owns the protocol; this skill owns honoring it in middleware. |
|
|
360
|
+
| Designing the full Content Security Policy or the broader hardening discipline | `security-fundamentals` | security-fundamentals owns the policy; middleware is one delivery surface. |
|
|
361
|
+
| The cross-cutting streaming model (Web Streams, SSE, backpressure) | `streaming-architecture` | Middleware can set headers for streamed responses but does not author the streaming logic. |
|
|
362
|
+
| The serialization/directive mechanics of `'use client'` and `'use server'` | `client-server-boundary` | Different boundary — that's the bundler component split, not the HTTP request edge. |
|
|
363
|
+
| The full webhook reliability story (HMAC verification, idempotency, retries) | `webhook-integration` | Webhook routes should generally be excluded from middleware; their handler owns the reliability discipline. |
|