@wazir-dev/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,1374 @@
|
|
|
1
|
+
# Web Vitals — Performance Expertise Module
|
|
2
|
+
|
|
3
|
+
> Core Web Vitals (LCP, INP, CLS) are Google's user-centric performance metrics that directly impact search rankings, user engagement, and conversion rates. They measure loading performance, interactivity, and visual stability — the three pillars of user-perceived web performance.
|
|
4
|
+
|
|
5
|
+
> **Impact:** Critical
|
|
6
|
+
> **Applies to:** Web
|
|
7
|
+
> **Key metrics:** Largest Contentful Paint (LCP), Interaction to Next Paint (INP), Cumulative Layout Shift (CLS)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why This Matters
|
|
12
|
+
|
|
13
|
+
### Search Ranking Impact
|
|
14
|
+
|
|
15
|
+
Core Web Vitals are a confirmed Google ranking signal since June 2021. Google evaluates
|
|
16
|
+
pages using the 75th percentile of real-user data collected via the Chrome User Experience
|
|
17
|
+
Report (CrUX). Pages failing CWV thresholds lose ranking eligibility for Top Stories
|
|
18
|
+
carousels and are disadvantaged in competitive SERPs. Sites have reported moving up 5-10
|
|
19
|
+
positions for competitive keywords solely by improving performance metrics.
|
|
20
|
+
|
|
21
|
+
As of 2025, only 48% of mobile websites and 56% of desktop websites pass all three Core
|
|
22
|
+
Web Vitals — meaning more than half the web still fails these thresholds, representing a
|
|
23
|
+
significant competitive opportunity.
|
|
24
|
+
|
|
25
|
+
### Real Business Case Studies
|
|
26
|
+
|
|
27
|
+
| Company | Optimization | Result |
|
|
28
|
+
|---------|-------------|--------|
|
|
29
|
+
| **Vodafone** | 31% LCP improvement | 8% more sales, 15% better lead-to-visit rate, 11% better cart-to-visit rate |
|
|
30
|
+
| **Swappie** | 55% LCP improvement, 91% CLS improvement | 42% jump in mobile revenue |
|
|
31
|
+
| **Rakuten** | Full CWV optimization | 33% higher conversion rate, 53% higher revenue per visitor |
|
|
32
|
+
| **T-Mobile** | Page speed overhaul | 20% fewer in-site issues, 60% increase in visit-to-order rate |
|
|
33
|
+
| **Google Flights** | Added `fetchpriority="high"` to hero image | 700ms LCP improvement |
|
|
34
|
+
| **Etsy** | Priority Hints on LCP elements | 4% LCP improvement (up to 20-30% in lab) |
|
|
35
|
+
|
|
36
|
+
**Source:** web.dev/case-studies/vodafone, web.dev/case-studies/vitals-business-impact
|
|
37
|
+
|
|
38
|
+
### The Conversion-Speed Relationship
|
|
39
|
+
|
|
40
|
+
Every 100ms improvement in page load time can boost conversion rates by up to 7%. A
|
|
41
|
+
healthcare provider that optimized mobile CWV scores saw a 43% increase in mobile
|
|
42
|
+
conversion rates. These numbers compound: for an e-commerce site doing $10M/year, a 1-second
|
|
43
|
+
LCP improvement could translate to $700K+ in additional revenue.
|
|
44
|
+
|
|
45
|
+
### FID to INP Transition (March 2024)
|
|
46
|
+
|
|
47
|
+
In March 2024, Google officially replaced First Input Delay (FID) with Interaction to Next
|
|
48
|
+
Paint (INP). This was significant because:
|
|
49
|
+
|
|
50
|
+
- **FID** only measured the delay of the *first* interaction and ignored processing time
|
|
51
|
+
- **INP** captures *every* interaction (clicks, taps, key presses) throughout the full page lifecycle
|
|
52
|
+
- **INP** reports the worst interaction at the 75th percentile, making it far harder to game
|
|
53
|
+
- The transition caused a ~5 percentage point drop in mobile CWV pass rates because sites
|
|
54
|
+
that appeared responsive under FID had slow later interactions that INP now captures
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Performance Budgets & Targets
|
|
59
|
+
|
|
60
|
+
### Official Thresholds
|
|
61
|
+
|
|
62
|
+
| Metric | Good | Needs Improvement | Poor |
|
|
63
|
+
|--------|------|-------------------|------|
|
|
64
|
+
| **LCP** | < 2.5s | 2.5s - 4.0s | > 4.0s |
|
|
65
|
+
| **INP** | < 200ms | 200ms - 500ms | > 500ms |
|
|
66
|
+
| **CLS** | < 0.1 | 0.1 - 0.25 | > 0.25 |
|
|
67
|
+
|
|
68
|
+
### How Thresholds Are Evaluated
|
|
69
|
+
|
|
70
|
+
Google uses the **75th percentile** (p75) of page visits over a rolling 28-day window.
|
|
71
|
+
This means 75% of your visitors must experience a "Good" score for the page to pass. This
|
|
72
|
+
is more demanding than a median — the bottom quartile of visits drags your score down.
|
|
73
|
+
|
|
74
|
+
### Mobile vs Desktop Differences
|
|
75
|
+
|
|
76
|
+
Mobile consistently scores worse than desktop due to:
|
|
77
|
+
- **CPU:** Mobile processors take 3-5x longer to parse and execute JavaScript
|
|
78
|
+
- **Network:** Cellular connections add 50-300ms of latency vs broadband
|
|
79
|
+
- **Rendering:** Smaller viewports mean different LCP elements (often text instead of images)
|
|
80
|
+
- **2025 data:** 48% mobile pass rate vs 56% desktop pass rate
|
|
81
|
+
|
|
82
|
+
### Per-Metric Pass Rates (2025 Web Almanac)
|
|
83
|
+
|
|
84
|
+
| Metric | Mobile Good | Desktop Good | Mobile Poor |
|
|
85
|
+
|--------|------------|-------------|-------------|
|
|
86
|
+
| **LCP** | ~60% | ~70% | ~15% |
|
|
87
|
+
| **INP** | ~77% | ~85% | ~6% |
|
|
88
|
+
| **CLS** | ~72% | ~78% | ~11% |
|
|
89
|
+
|
|
90
|
+
INP has the highest pass rate individually, but it is the most commonly *failed* metric
|
|
91
|
+
when combined — 43% of sites that fail CWV fail specifically on INP.
|
|
92
|
+
|
|
93
|
+
### Setting Internal Targets
|
|
94
|
+
|
|
95
|
+
Do not target the threshold boundary. Set internal budgets at 70-80% of the "Good"
|
|
96
|
+
threshold to provide a safety margin for traffic spikes and edge cases:
|
|
97
|
+
|
|
98
|
+
| Metric | Recommended Internal Budget |
|
|
99
|
+
|--------|---------------------------|
|
|
100
|
+
| **LCP** | < 1.8s (28% buffer from 2.5s) |
|
|
101
|
+
| **INP** | < 150ms (25% buffer from 200ms) |
|
|
102
|
+
| **CLS** | < 0.05 (50% buffer from 0.1) |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Measurement & Profiling
|
|
107
|
+
|
|
108
|
+
### Lab Tools (Synthetic / Controlled)
|
|
109
|
+
|
|
110
|
+
| Tool | What It Measures | Use Case |
|
|
111
|
+
|------|-----------------|----------|
|
|
112
|
+
| **Lighthouse** | LCP, CLS, TBT (proxy for INP) | Development-time audits, CI/CD gates |
|
|
113
|
+
| **Chrome DevTools Performance Panel** | LCP, CLS, INP, long tasks, layout shifts | Debugging specific interactions |
|
|
114
|
+
| **WebPageTest** | LCP, CLS, TBT, filmstrip, waterfall | Deep network/rendering analysis |
|
|
115
|
+
| **PageSpeed Insights** | Lab + Field data combined | Quick assessment with CrUX overlay |
|
|
116
|
+
|
|
117
|
+
**Important:** Lab tools cannot measure INP directly (no real user interactions). Total
|
|
118
|
+
Blocking Time (TBT) is used as a lab proxy — TBT correlates with INP at ~0.8 correlation.
|
|
119
|
+
|
|
120
|
+
### Field Tools (Real User Monitoring)
|
|
121
|
+
|
|
122
|
+
| Tool | Data Source | Granularity | Update Frequency |
|
|
123
|
+
|------|------------|------------|-----------------|
|
|
124
|
+
| **CrUX (Chrome UX Report)** | Real Chrome users | Origin + URL level | Daily (API), Monthly (BigQuery) |
|
|
125
|
+
| **Search Console CWV Report** | CrUX data | URL groups | Rolling 28-day window |
|
|
126
|
+
| **PageSpeed Insights** | CrUX + Lighthouse | Per-URL | On-demand |
|
|
127
|
+
| **RUM providers** (SpeedCurve, DebugBear, Calibre) | Your users specifically | Per-page, per-segment | Real-time |
|
|
128
|
+
|
|
129
|
+
### JavaScript API: web-vitals Library (v5)
|
|
130
|
+
|
|
131
|
+
The `web-vitals` library (maintained by Google Chrome team) is the standard way to collect
|
|
132
|
+
CWV data from real users. It uses the `PerformanceObserver` API with the `buffered` flag,
|
|
133
|
+
meaning it captures metrics even if loaded late.
|
|
134
|
+
|
|
135
|
+
#### Installation and Basic Usage
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm install web-vitals
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// Basic usage — collect all three Core Web Vitals
|
|
143
|
+
import { onLCP, onINP, onCLS } from 'web-vitals';
|
|
144
|
+
|
|
145
|
+
function sendToAnalytics(metric) {
|
|
146
|
+
const body = JSON.stringify({
|
|
147
|
+
name: metric.name, // "LCP", "INP", or "CLS"
|
|
148
|
+
value: metric.value, // The metric value
|
|
149
|
+
rating: metric.rating, // "good", "needs-improvement", or "poor"
|
|
150
|
+
delta: metric.delta, // Change since last report
|
|
151
|
+
id: metric.id, // Unique ID for this metric instance
|
|
152
|
+
navigationType: metric.navigationType, // "navigate", "reload", "back-forward", etc.
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Use sendBeacon for reliability during page unload
|
|
156
|
+
if (navigator.sendBeacon) {
|
|
157
|
+
navigator.sendBeacon('/analytics', body);
|
|
158
|
+
} else {
|
|
159
|
+
fetch('/analytics', { body, method: 'POST', keepalive: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
onLCP(sendToAnalytics);
|
|
164
|
+
onINP(sendToAnalytics);
|
|
165
|
+
onCLS(sendToAnalytics);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Attribution Build (for debugging root causes)
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// Attribution build adds diagnostic data to each metric
|
|
172
|
+
import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
|
|
173
|
+
|
|
174
|
+
onLCP((metric) => {
|
|
175
|
+
console.log('LCP element:', metric.attribution.element);
|
|
176
|
+
console.log('LCP resource URL:', metric.attribution.url);
|
|
177
|
+
console.log('Time to first byte:', metric.attribution.timeToFirstByte);
|
|
178
|
+
console.log('Resource load delay:', metric.attribution.resourceLoadDelay);
|
|
179
|
+
console.log('Resource load time:', metric.attribution.resourceLoadTime);
|
|
180
|
+
console.log('Element render delay:', metric.attribution.elementRenderDelay);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
onINP((metric) => {
|
|
184
|
+
console.log('INP event type:', metric.attribution.interactionType);
|
|
185
|
+
console.log('INP target:', metric.attribution.interactionTarget);
|
|
186
|
+
console.log('Input delay:', metric.attribution.inputDelay);
|
|
187
|
+
console.log('Processing duration:', metric.attribution.processingDuration);
|
|
188
|
+
console.log('Presentation delay:', metric.attribution.presentationDelay);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
onCLS((metric) => {
|
|
192
|
+
console.log('Largest shift target:', metric.attribution.largestShiftTarget);
|
|
193
|
+
console.log('Largest shift value:', metric.attribution.largestShiftValue);
|
|
194
|
+
console.log('Largest shift time:', metric.attribution.largestShiftTime);
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### CDN Usage (No Build Step)
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<script type="module">
|
|
202
|
+
import { onCLS, onINP, onLCP } from 'https://unpkg.com/web-vitals@5?module';
|
|
203
|
+
onCLS(console.log);
|
|
204
|
+
onINP(console.log);
|
|
205
|
+
onLCP(console.log);
|
|
206
|
+
</script>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Critical rule:** Never call `onLCP()`, `onINP()`, or `onCLS()` more than once per page
|
|
210
|
+
load. Each call creates a `PerformanceObserver` instance and event listeners for the
|
|
211
|
+
lifetime of the page — calling them repeatedly causes memory leaks.
|
|
212
|
+
|
|
213
|
+
### CrUX API for Automated Monitoring
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
// Query CrUX API for origin-level data
|
|
217
|
+
const API_KEY = 'YOUR_API_KEY';
|
|
218
|
+
const url = `https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${API_KEY}`;
|
|
219
|
+
|
|
220
|
+
const response = await fetch(url, {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
body: JSON.stringify({
|
|
223
|
+
origin: 'https://example.com',
|
|
224
|
+
formFactor: 'PHONE',
|
|
225
|
+
metrics: [
|
|
226
|
+
'largest_contentful_paint',
|
|
227
|
+
'interaction_to_next_paint',
|
|
228
|
+
'cumulative_layout_shift'
|
|
229
|
+
]
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const data = await response.json();
|
|
234
|
+
// data.record.metrics.largest_contentful_paint.percentiles.p75
|
|
235
|
+
// data.record.metrics.largest_contentful_paint.histogram[0].density (good %)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
CrUX data is also available in BigQuery (`chrome-ux-report` project) for large-scale
|
|
239
|
+
analysis across millions of origins, updated monthly on the second Tuesday after collection.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Common Bottlenecks
|
|
244
|
+
|
|
245
|
+
### LCP Bottlenecks
|
|
246
|
+
|
|
247
|
+
LCP measures when the largest content element (image, video poster, text block, or
|
|
248
|
+
background image) becomes visible in the viewport.
|
|
249
|
+
|
|
250
|
+
| Bottleneck | Impact | Frequency |
|
|
251
|
+
|-----------|--------|-----------|
|
|
252
|
+
| **Slow server response (high TTFB)** | Delays everything; a 1s TTFB makes 2.5s LCP nearly impossible | ~35% of poor LCP pages |
|
|
253
|
+
| **Render-blocking CSS/JS** | Browser cannot render until critical CSS is parsed and blocking JS executes | ~30% of poor LCP pages |
|
|
254
|
+
| **Slow resource load time** | Large unoptimized images, no CDN, no compression | ~25% of poor LCP pages |
|
|
255
|
+
| **Client-side rendering** | Content not in HTML; must download, parse, and execute JS first | ~20% of SPA pages |
|
|
256
|
+
| **Resource discovery delay** | LCP image referenced in CSS or loaded via JS, invisible to preload scanner | ~15% of poor LCP pages |
|
|
257
|
+
| **Lazy loading above-the-fold images** | `loading="lazy"` on LCP image delays its fetch until layout | Common anti-pattern |
|
|
258
|
+
| **No image optimization** | Serving 2MB PNG instead of 200KB WebP/AVIF | Very common |
|
|
259
|
+
| **Unoptimized web fonts blocking text** | Text is the LCP element but invisible during font load (FOIT) | ~10% of text-LCP pages |
|
|
260
|
+
|
|
261
|
+
### INP Bottlenecks
|
|
262
|
+
|
|
263
|
+
INP measures the latency from user input to the next visual update. It has three phases:
|
|
264
|
+
**input delay** (waiting for main thread), **processing time** (event handler execution),
|
|
265
|
+
and **presentation delay** (rendering after handler completes).
|
|
266
|
+
|
|
267
|
+
| Bottleneck | Phase Affected | Impact |
|
|
268
|
+
|-----------|---------------|--------|
|
|
269
|
+
| **Long tasks (>50ms) on main thread** | Input delay | User taps during a long task; response waits for task completion |
|
|
270
|
+
| **Excessive JavaScript execution** | Processing time | Event handlers do too much synchronous work |
|
|
271
|
+
| **Layout thrashing** | Processing time | Repeated read/write cycles force synchronous layout recalculations |
|
|
272
|
+
| **Large DOM size (>1500 nodes)** | Presentation delay | Browser takes longer to recalculate styles and paint after changes |
|
|
273
|
+
| **Heavy third-party scripts** | Input delay | Analytics, ads, chat widgets block the main thread |
|
|
274
|
+
| **Forced synchronous layout** | Processing time | Reading `offsetHeight` after writing to DOM triggers immediate reflow |
|
|
275
|
+
| **Unoptimized React re-renders** | Processing time | State changes triggering large subtree re-renders |
|
|
276
|
+
| **No task yielding** | Input delay | Long-running computations never yield to let interactions through |
|
|
277
|
+
|
|
278
|
+
**Key stat:** Fewer than 25% of websites keep task duration below the recommended 50ms
|
|
279
|
+
threshold (HTTP Archive 2025 Web Almanac).
|
|
280
|
+
|
|
281
|
+
### CLS Bottlenecks
|
|
282
|
+
|
|
283
|
+
CLS measures unexpected layout shifts during the page lifecycle, scored by impact fraction
|
|
284
|
+
times distance fraction.
|
|
285
|
+
|
|
286
|
+
| Bottleneck | Share of CLS Issues | Impact |
|
|
287
|
+
|-----------|-------------------|--------|
|
|
288
|
+
| **Images/videos without dimensions** | ~60% | Browser allocates 0px then reflows when media loads |
|
|
289
|
+
| **Web fonts causing text reflow (FOIT/FOUT)** | ~25% | Fallback font metrics differ from web font, text reflows |
|
|
290
|
+
| **Dynamically injected content** | ~15% | Ads, banners, cookie notices push content down |
|
|
291
|
+
| **Late-loading CSS** | Common | Styles apply after content renders, causing shifts |
|
|
292
|
+
| **Animations not using `transform`** | Moderate | Animating `top`/`left`/`width`/`height` causes layout shifts |
|
|
293
|
+
| **Async-loaded embeds without placeholders** | Moderate | iframes, maps, social widgets have unknown dimensions |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Optimization Patterns
|
|
298
|
+
|
|
299
|
+
### LCP Optimization
|
|
300
|
+
|
|
301
|
+
#### 1. Eliminate Resource Discovery Delay
|
|
302
|
+
|
|
303
|
+
The LCP image must be discoverable in the HTML source by the browser's preload scanner.
|
|
304
|
+
If it is referenced only in CSS (`background-image`) or loaded via JavaScript, the browser
|
|
305
|
+
cannot start fetching it until those resources are parsed.
|
|
306
|
+
|
|
307
|
+
```html
|
|
308
|
+
<!-- BEFORE: Image only discoverable after CSS parses (adds 200-800ms) -->
|
|
309
|
+
<div class="hero" style="background-image: url('/hero.jpg')"></div>
|
|
310
|
+
|
|
311
|
+
<!-- AFTER: Image discoverable immediately by preload scanner -->
|
|
312
|
+
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
|
|
313
|
+
<img src="/hero.webp" alt="Hero" fetchpriority="high"
|
|
314
|
+
width="1200" height="600" decoding="async">
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Measured impact:** Google Flights saw a 700ms LCP improvement from adding
|
|
318
|
+
`fetchpriority="high"` alone. Google's own tests improved LCP from 2.6s to 1.9s.
|
|
319
|
+
|
|
320
|
+
#### 2. Optimize Server Response Time
|
|
321
|
+
|
|
322
|
+
```nginx
|
|
323
|
+
# nginx: Enable gzip + brotli compression
|
|
324
|
+
gzip on;
|
|
325
|
+
gzip_types text/html text/css application/javascript image/svg+xml;
|
|
326
|
+
|
|
327
|
+
# Enable brotli (requires ngx_brotli module)
|
|
328
|
+
brotli on;
|
|
329
|
+
brotli_types text/html text/css application/javascript image/svg+xml;
|
|
330
|
+
|
|
331
|
+
# Set aggressive caching for immutable assets
|
|
332
|
+
location /assets/ {
|
|
333
|
+
expires 1y;
|
|
334
|
+
add_header Cache-Control "public, immutable";
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Trade-off:** Brotli compression achieves 15-25% smaller files than gzip but requires
|
|
339
|
+
more CPU for compression. Use brotli for static assets (pre-compressed) and gzip for
|
|
340
|
+
dynamic content.
|
|
341
|
+
|
|
342
|
+
#### 3. Responsive Images with Modern Formats
|
|
343
|
+
|
|
344
|
+
```html
|
|
345
|
+
<!-- BEFORE: Single 2MB PNG for all devices -->
|
|
346
|
+
<img src="/hero.png" alt="Hero">
|
|
347
|
+
|
|
348
|
+
<!-- AFTER: Format negotiation + responsive sizing -->
|
|
349
|
+
<picture>
|
|
350
|
+
<source srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1200.avif 1200w"
|
|
351
|
+
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px"
|
|
352
|
+
type="image/avif">
|
|
353
|
+
<source srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
|
|
354
|
+
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px"
|
|
355
|
+
type="image/webp">
|
|
356
|
+
<img src="/hero-800.jpg" alt="Hero" width="1200" height="600"
|
|
357
|
+
fetchpriority="high" decoding="async">
|
|
358
|
+
</picture>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Measured savings:**
|
|
362
|
+
- AVIF: 50-70% smaller than JPEG at equivalent quality
|
|
363
|
+
- WebP: 25-35% smaller than JPEG
|
|
364
|
+
- On a 4G mobile connection, a 200KB WebP loads in ~400ms vs 800ms for a 500KB JPEG
|
|
365
|
+
|
|
366
|
+
**Trade-off:** AVIF encoding is 10-20x slower than JPEG. Pre-generate at build time or
|
|
367
|
+
use an image CDN (Cloudinary, imgix, Cloudflare Images) for on-the-fly conversion.
|
|
368
|
+
|
|
369
|
+
#### 4. Inline Critical CSS, Defer the Rest
|
|
370
|
+
|
|
371
|
+
```html
|
|
372
|
+
<head>
|
|
373
|
+
<!-- Inline only above-the-fold CSS (target: <14KB to fit in first TCP round-trip) -->
|
|
374
|
+
<style>
|
|
375
|
+
/* Critical CSS extracted by tools like critters or critical */
|
|
376
|
+
.hero { display: flex; align-items: center; min-height: 60vh; }
|
|
377
|
+
.hero img { width: 100%; height: auto; }
|
|
378
|
+
nav { display: flex; gap: 1rem; padding: 1rem; }
|
|
379
|
+
</style>
|
|
380
|
+
|
|
381
|
+
<!-- Defer non-critical CSS -->
|
|
382
|
+
<link rel="preload" href="/styles.css" as="style"
|
|
383
|
+
onload="this.onload=null;this.rel='stylesheet'">
|
|
384
|
+
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
|
|
385
|
+
</head>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Measured impact:** Inlining critical CSS typically reduces LCP by 300-800ms on 3G
|
|
389
|
+
connections by eliminating a render-blocking round-trip.
|
|
390
|
+
|
|
391
|
+
**Trade-off:** Inlined CSS is not cached separately. If critical CSS exceeds ~14KB, it
|
|
392
|
+
no longer fits in the initial TCP congestion window and loses its advantage. Use tools
|
|
393
|
+
like `critters` (Webpack/Vite plugin) to automate extraction.
|
|
394
|
+
|
|
395
|
+
### INP Optimization
|
|
396
|
+
|
|
397
|
+
#### 1. Break Up Long Tasks with `scheduler.yield()`
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// BEFORE: Single long task blocks main thread for 300ms+
|
|
401
|
+
function processLargeDataset(items) {
|
|
402
|
+
for (const item of items) {
|
|
403
|
+
heavyComputation(item); // 5ms per item, 1000 items = 5000ms blocked
|
|
404
|
+
updateDOM(item);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// AFTER: Yield to main thread, allowing interactions to process
|
|
409
|
+
async function processLargeDataset(items) {
|
|
410
|
+
const BATCH_SIZE = 50; // ~250ms per batch, then yield
|
|
411
|
+
for (let i = 0; i < items.length; i += BATCH_SIZE) {
|
|
412
|
+
const batch = items.slice(i, i + BATCH_SIZE);
|
|
413
|
+
for (const item of batch) {
|
|
414
|
+
heavyComputation(item);
|
|
415
|
+
updateDOM(item);
|
|
416
|
+
}
|
|
417
|
+
// Yield to main thread — interactions can now be processed
|
|
418
|
+
await scheduler.yield();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Key advantage of `scheduler.yield()` over `setTimeout(0)`:** `scheduler.yield()`
|
|
424
|
+
schedules the continuation at the *front* of the task queue, not the back. This means
|
|
425
|
+
your remaining work runs immediately after pending user interactions, rather than after
|
|
426
|
+
all other queued tasks. Result: same INP improvement with less total processing delay.
|
|
427
|
+
|
|
428
|
+
**Browser support:** Chrome 129+ (September 2024). For older browsers, use a polyfill:
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
// Polyfill for browsers without scheduler.yield
|
|
432
|
+
async function yieldToMain() {
|
|
433
|
+
if ('scheduler' in globalThis && 'yield' in scheduler) {
|
|
434
|
+
return scheduler.yield();
|
|
435
|
+
}
|
|
436
|
+
return new Promise(resolve => setTimeout(resolve, 0));
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### 2. Debounce and Throttle Expensive Handlers
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
// BEFORE: Scroll handler fires 60+ times/second, each triggering layout
|
|
444
|
+
window.addEventListener('scroll', () => {
|
|
445
|
+
const rect = element.getBoundingClientRect(); // forces layout
|
|
446
|
+
element.style.transform = `translateY(${rect.top}px)`; // triggers paint
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// AFTER: Use requestAnimationFrame to batch to paint cycle
|
|
450
|
+
let ticking = false;
|
|
451
|
+
window.addEventListener('scroll', () => {
|
|
452
|
+
if (!ticking) {
|
|
453
|
+
requestAnimationFrame(() => {
|
|
454
|
+
const rect = element.getBoundingClientRect();
|
|
455
|
+
element.style.transform = `translateY(${rect.top}px)`;
|
|
456
|
+
ticking = false;
|
|
457
|
+
});
|
|
458
|
+
ticking = true;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Measured impact:** Reducing scroll handler frequency from 60/s to 1/frame typically
|
|
464
|
+
cuts INP for scroll-triggered interactions by 40-70%.
|
|
465
|
+
|
|
466
|
+
#### 3. Use `content-visibility` for Off-Screen Content
|
|
467
|
+
|
|
468
|
+
```css
|
|
469
|
+
/* Browser skips rendering for off-screen sections entirely */
|
|
470
|
+
.below-fold-section {
|
|
471
|
+
content-visibility: auto;
|
|
472
|
+
contain-intrinsic-size: auto 500px; /* estimated height to prevent CLS */
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Measured impact:** Chrome team reported rendering time reductions of 50-70% for pages
|
|
477
|
+
with many off-screen sections. The `contain-intrinsic-size` prevents CLS by reserving
|
|
478
|
+
space.
|
|
479
|
+
|
|
480
|
+
**Trade-off:** `content-visibility: auto` can cause `find-in-page` (Ctrl+F) to miss
|
|
481
|
+
content in some edge cases. Also, any element with `content-visibility: auto` that becomes
|
|
482
|
+
visible will trigger a rendering cost at that moment.
|
|
483
|
+
|
|
484
|
+
#### 4. Move Heavy Computation to Web Workers
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
// BEFORE: JSON parsing blocks main thread for 200ms+
|
|
488
|
+
const data = JSON.parse(hugeJsonString); // blocks INP
|
|
489
|
+
|
|
490
|
+
// AFTER: Offload to Web Worker
|
|
491
|
+
// worker.js
|
|
492
|
+
self.onmessage = (e) => {
|
|
493
|
+
const data = JSON.parse(e.data);
|
|
494
|
+
self.postMessage(data);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// main.js
|
|
498
|
+
const worker = new Worker('/worker.js');
|
|
499
|
+
worker.postMessage(hugeJsonString);
|
|
500
|
+
worker.onmessage = (e) => {
|
|
501
|
+
renderData(e.data); // Only DOM update on main thread
|
|
502
|
+
};
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Trade-off:** Web Workers have no DOM access and data transfer via `postMessage` has
|
|
506
|
+
serialization cost. Use `Transferable` objects (ArrayBuffer) for large binary data to
|
|
507
|
+
avoid the copy overhead. Structured clone for objects typically adds 1-5ms per MB.
|
|
508
|
+
|
|
509
|
+
### CLS Optimization
|
|
510
|
+
|
|
511
|
+
#### 1. Always Set Explicit Dimensions on Media
|
|
512
|
+
|
|
513
|
+
```html
|
|
514
|
+
<!-- BEFORE: No dimensions — browser allocates 0px, then shifts -->
|
|
515
|
+
<img src="/photo.jpg" alt="Product">
|
|
516
|
+
|
|
517
|
+
<!-- AFTER: Explicit width/height lets browser reserve space -->
|
|
518
|
+
<img src="/photo.jpg" alt="Product" width="800" height="600"
|
|
519
|
+
loading="lazy" decoding="async">
|
|
520
|
+
|
|
521
|
+
<!-- AFTER (modern CSS approach): aspect-ratio for responsive images -->
|
|
522
|
+
<style>
|
|
523
|
+
.responsive-img {
|
|
524
|
+
width: 100%;
|
|
525
|
+
height: auto;
|
|
526
|
+
aspect-ratio: 4 / 3; /* Browser reserves correct space before load */
|
|
527
|
+
}
|
|
528
|
+
</style>
|
|
529
|
+
<img class="responsive-img" src="/photo.jpg" alt="Product"
|
|
530
|
+
loading="lazy" decoding="async">
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Measured impact:** Adding `width`/`height` attributes to images eliminates ~60% of all
|
|
534
|
+
CLS issues across the web.
|
|
535
|
+
|
|
536
|
+
#### 2. Optimize Font Loading to Prevent Text Reflow
|
|
537
|
+
|
|
538
|
+
```css
|
|
539
|
+
/* Option A: font-display: swap (shows text immediately with fallback) */
|
|
540
|
+
@font-face {
|
|
541
|
+
font-family: 'CustomFont';
|
|
542
|
+
src: url('/fonts/custom.woff2') format('woff2');
|
|
543
|
+
font-display: swap;
|
|
544
|
+
/* Risk: fallback → custom font swap causes CLS if metrics differ */
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* Option B: font-display: optional (best for CLS, may not show custom font) */
|
|
548
|
+
@font-face {
|
|
549
|
+
font-family: 'CustomFont';
|
|
550
|
+
src: url('/fonts/custom.woff2') format('woff2');
|
|
551
|
+
font-display: optional;
|
|
552
|
+
/* Font only used if already cached; zero CLS guaranteed */
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* Option C: Size-adjust fallback (best of both worlds) */
|
|
556
|
+
@font-face {
|
|
557
|
+
font-family: 'CustomFont Fallback';
|
|
558
|
+
src: local('Arial');
|
|
559
|
+
size-adjust: 105.2%; /* Match custom font metrics */
|
|
560
|
+
ascent-override: 95%;
|
|
561
|
+
descent-override: 22%;
|
|
562
|
+
line-gap-override: 0%;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
body {
|
|
566
|
+
font-family: 'CustomFont', 'CustomFont Fallback', sans-serif;
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Preload critical fonts:**
|
|
571
|
+
```html
|
|
572
|
+
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Trade-offs:**
|
|
576
|
+
- `font-display: swap` — zero FOIT but may cause CLS on text reflow (~0.02-0.08 CLS)
|
|
577
|
+
- `font-display: optional` — zero CLS but first-time visitors may not see custom font
|
|
578
|
+
- `size-adjust` fallback — near-zero CLS with custom font display, but requires per-font metric tuning
|
|
579
|
+
|
|
580
|
+
#### 3. Reserve Space for Dynamic Content
|
|
581
|
+
|
|
582
|
+
```html
|
|
583
|
+
<!-- BEFORE: Ad loads and pushes content down -->
|
|
584
|
+
<div class="content">
|
|
585
|
+
<p>Article text here...</p>
|
|
586
|
+
<div id="ad-slot"></div> <!-- 0px until ad loads -->
|
|
587
|
+
<p>More content...</p>
|
|
588
|
+
</div>
|
|
589
|
+
|
|
590
|
+
<!-- AFTER: Reserve space with min-height -->
|
|
591
|
+
<div class="content">
|
|
592
|
+
<p>Article text here...</p>
|
|
593
|
+
<div id="ad-slot" style="min-height: 250px; background: #f0f0f0;">
|
|
594
|
+
<!-- 250px reserved for standard IAB medium rectangle ad -->
|
|
595
|
+
</div>
|
|
596
|
+
<p>More content...</p>
|
|
597
|
+
</div>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
**For cookie banners and notification bars:**
|
|
601
|
+
```css
|
|
602
|
+
/* Reserve space at top/bottom for banners that push content */
|
|
603
|
+
.cookie-banner-placeholder {
|
|
604
|
+
min-height: 80px; /* match your banner height */
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/* OR: Use overlay positioning (no layout shift) */
|
|
608
|
+
.cookie-banner {
|
|
609
|
+
position: fixed;
|
|
610
|
+
bottom: 0;
|
|
611
|
+
left: 0;
|
|
612
|
+
right: 0;
|
|
613
|
+
z-index: 1000;
|
|
614
|
+
/* Fixed positioning does NOT cause layout shifts */
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### 4. Use CSS `transform` for Animations
|
|
619
|
+
|
|
620
|
+
```css
|
|
621
|
+
/* BEFORE: Animating layout properties causes CLS */
|
|
622
|
+
.slide-in {
|
|
623
|
+
animation: slideIn 0.3s ease;
|
|
624
|
+
}
|
|
625
|
+
@keyframes slideIn {
|
|
626
|
+
from { margin-left: -100%; } /* triggers layout shift */
|
|
627
|
+
to { margin-left: 0; }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/* AFTER: transform animations do not cause layout shifts */
|
|
631
|
+
.slide-in {
|
|
632
|
+
animation: slideIn 0.3s ease;
|
|
633
|
+
}
|
|
634
|
+
@keyframes slideIn {
|
|
635
|
+
from { transform: translateX(-100%); } /* compositor-only, no CLS */
|
|
636
|
+
to { transform: translateX(0); }
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Rule:** Only `transform` and `opacity` animations are compositor-only and CLS-free.
|
|
641
|
+
Animating `top`, `left`, `width`, `height`, `margin`, or `padding` triggers layout and
|
|
642
|
+
contributes to CLS.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Anti-Patterns
|
|
647
|
+
|
|
648
|
+
### 1. Lazy Loading the LCP Image
|
|
649
|
+
Adding `loading="lazy"` to the largest above-the-fold image delays its fetch until the
|
|
650
|
+
browser determines it is near the viewport — after layout. This adds 200-1000ms to LCP.
|
|
651
|
+
**Fix:** Never lazy-load above-the-fold images. Use `fetchpriority="high"` instead.
|
|
652
|
+
|
|
653
|
+
### 2. Preloading Everything
|
|
654
|
+
Excessive `<link rel="preload">` tags cause bandwidth contention, delaying the resources
|
|
655
|
+
that actually matter. Chrome limits effective preloads to ~6 concurrent connections per
|
|
656
|
+
origin. **Fix:** Preload only the LCP image and critical font (1-2 preloads max).
|
|
657
|
+
|
|
658
|
+
### 3. Third-Party Script Tags in `<head>`
|
|
659
|
+
Synchronous third-party scripts (analytics, tag managers, chat widgets) block rendering.
|
|
660
|
+
A single 100KB synchronous script on 3G adds 500ms+ to LCP.
|
|
661
|
+
**Fix:** Load all third-party scripts with `async` or `defer`. Use `requestIdleCallback`
|
|
662
|
+
or `setTimeout` for non-critical scripts.
|
|
663
|
+
|
|
664
|
+
### 4. Using `display: none` Instead of `content-visibility`
|
|
665
|
+
`display: none` still parses and constructs the DOM for hidden elements. For large
|
|
666
|
+
off-screen sections, this wastes CPU during interactions.
|
|
667
|
+
**Fix:** Use `content-visibility: auto` for sections below the fold.
|
|
668
|
+
|
|
669
|
+
### 5. Placeholder Spinners That Cause Layout Shift
|
|
670
|
+
Replacing a spinner (50px tall) with actual content (300px tall) causes a 250px shift.
|
|
671
|
+
**Fix:** Use skeleton screens that match the final content dimensions.
|
|
672
|
+
|
|
673
|
+
### 6. Importing Entire Libraries for Small Features
|
|
674
|
+
`import _ from 'lodash'` pulls in ~72KB minified for functions like `debounce` (0.3KB).
|
|
675
|
+
**Fix:** Use `import debounce from 'lodash-es/debounce'` or native alternatives.
|
|
676
|
+
|
|
677
|
+
### 7. Unoptimized `useEffect` in React Causing INP Issues
|
|
678
|
+
```javascript
|
|
679
|
+
// ANTI-PATTERN: useEffect with missing dependency causes re-render storms
|
|
680
|
+
useEffect(() => {
|
|
681
|
+
setData(transform(rawData)); // triggers re-render every time rawData ref changes
|
|
682
|
+
}); // missing dependency array = runs every render
|
|
683
|
+
```
|
|
684
|
+
**Fix:** Use `useMemo` for expensive transforms, add correct dependency arrays.
|
|
685
|
+
|
|
686
|
+
### 8. Client-Side Rendering for Content-Heavy Pages
|
|
687
|
+
SPAs that fetch data after hydration add 1-3 seconds to LCP because the browser must:
|
|
688
|
+
download JS bundle > parse > execute > fetch data > render. Each step is sequential.
|
|
689
|
+
**Fix:** Use SSR/SSG for content-heavy pages, CSR only for authenticated dashboards.
|
|
690
|
+
|
|
691
|
+
### 9. Not Setting `decoding="async"` on Non-LCP Images
|
|
692
|
+
Without `decoding="async"`, image decoding can block the main thread for 10-50ms per
|
|
693
|
+
large image, accumulating into INP-impacting long tasks.
|
|
694
|
+
**Fix:** Add `decoding="async"` to all images except the LCP image.
|
|
695
|
+
|
|
696
|
+
### 10. Using `@import` in CSS Files
|
|
697
|
+
CSS `@import` creates sequential request chains. Each `@import` adds a full round-trip:
|
|
698
|
+
```css
|
|
699
|
+
/* style.css — ANTI-PATTERN */
|
|
700
|
+
@import url('reset.css'); /* fetch 1: 200ms */
|
|
701
|
+
@import url('layout.css'); /* fetch 2: waits for fetch 1, +200ms */
|
|
702
|
+
@import url('theme.css'); /* fetch 3: waits for fetch 2, +200ms */
|
|
703
|
+
/* Total: 600ms of sequential fetching */
|
|
704
|
+
```
|
|
705
|
+
**Fix:** Use `<link>` tags in HTML (fetched in parallel) or a CSS bundler.
|
|
706
|
+
|
|
707
|
+
### 11. Not Using a CDN for Static Assets
|
|
708
|
+
Serving images from a single origin server adds 50-300ms of latency for distant users.
|
|
709
|
+
A CDN reduces this to 5-20ms by serving from edge nodes.
|
|
710
|
+
**Fix:** Use a CDN for all static assets. Cost: ~$0.01-0.08/GB, trivial for most sites.
|
|
711
|
+
|
|
712
|
+
### 12. Dynamic `import()` on the Critical Path
|
|
713
|
+
```javascript
|
|
714
|
+
// ANTI-PATTERN: Dynamic import delays the hero component render
|
|
715
|
+
const HeroSection = lazy(() => import('./HeroSection'));
|
|
716
|
+
// This adds a network round-trip to LCP
|
|
717
|
+
```
|
|
718
|
+
**Fix:** Statically import above-the-fold components. Use dynamic `import()` only for
|
|
719
|
+
below-the-fold or interaction-triggered components.
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
## Architecture-Level Decisions
|
|
724
|
+
|
|
725
|
+
### Rendering Strategy Impact on LCP
|
|
726
|
+
|
|
727
|
+
| Strategy | Typical LCP | Pros | Cons |
|
|
728
|
+
|----------|------------|------|------|
|
|
729
|
+
| **SSR** | 1.0-2.0s | Content in first HTML response; best LCP | Server CPU cost; TTFB depends on server speed |
|
|
730
|
+
| **SSG** | 0.5-1.5s | Pre-built HTML; fastest TTFB from CDN | Not suitable for dynamic/personalized content |
|
|
731
|
+
| **CSR (SPA)** | 2.5-5.0s | Rich interactivity; simpler deployment | Worst LCP; requires JS to render any content |
|
|
732
|
+
| **Streaming SSR** | 0.8-1.8s | Progressive HTML delivery; good LCP + INP | Complexity; requires framework support |
|
|
733
|
+
| **Islands Architecture** | 0.6-1.5s | Minimal JS; near-SSG LCP with targeted interactivity | Newer pattern; limited framework support |
|
|
734
|
+
| **RSC (React Server Components)** | 0.8-1.8s | Zero client JS for server components; streaming | React ecosystem only; learning curve |
|
|
735
|
+
|
|
736
|
+
**Key finding:** Switching from CSR to SSR/SSG typically cuts LCP by 40-60% (web.dev).
|
|
737
|
+
|
|
738
|
+
### Streaming HTML
|
|
739
|
+
|
|
740
|
+
Streaming SSR sends HTML chunks as they are generated, rather than waiting for the entire
|
|
741
|
+
page to render on the server. This improves both TTFB and LCP:
|
|
742
|
+
|
|
743
|
+
```
|
|
744
|
+
Traditional SSR: [Server renders 2s] → [Send HTML] → [Browser paints at 2.5s]
|
|
745
|
+
Streaming SSR: [Server starts] → [Send <head> at 100ms] → [Send hero at 300ms] → [Browser paints at 800ms]
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Framework support:**
|
|
749
|
+
- Next.js: Built-in with App Router (`loading.js` for streaming boundaries)
|
|
750
|
+
- Remix: Native streaming with `defer()` loaders
|
|
751
|
+
- SvelteKit: Streaming by default in SSR mode
|
|
752
|
+
- Astro: Streaming SSR via adapter configuration
|
|
753
|
+
|
|
754
|
+
### Islands Architecture (Astro, Fresh, Marko)
|
|
755
|
+
|
|
756
|
+
Islands isolate interactive components into "islands" of JavaScript within a sea of
|
|
757
|
+
static HTML. The browser only downloads and hydrates JavaScript for interactive components.
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
Traditional SPA: [Download 500KB JS] → [Parse] → [Hydrate entire page] → [Interactive]
|
|
761
|
+
Islands: [Static HTML paints immediately] → [Download 50KB JS for 2 islands] → [Hydrate only islands]
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**Impact on CWV:**
|
|
765
|
+
- **LCP:** Near-SSG performance because HTML is static
|
|
766
|
+
- **INP:** Better because less JavaScript means fewer long tasks during hydration
|
|
767
|
+
- **CLS:** Better because static HTML has stable layout from first paint
|
|
768
|
+
|
|
769
|
+
**Trade-off:** Islands architecture requires decomposing your UI into static vs interactive
|
|
770
|
+
parts at the component level, which adds architectural complexity. Not all UI patterns map
|
|
771
|
+
cleanly to this model (e.g., highly interactive dashboards).
|
|
772
|
+
|
|
773
|
+
### Progressive Enhancement for INP
|
|
774
|
+
|
|
775
|
+
Build core functionality in HTML/CSS, enhance with JavaScript. If a form works without
|
|
776
|
+
JS (via standard `<form>` submission), then JS failures or slow loads do not block
|
|
777
|
+
interactions. This architectural choice guarantees zero INP for core flows before JS loads.
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## Testing & Regression Prevention
|
|
782
|
+
|
|
783
|
+
### Lighthouse CI Setup
|
|
784
|
+
|
|
785
|
+
```yaml
|
|
786
|
+
# .github/workflows/lighthouse-ci.yml
|
|
787
|
+
name: Lighthouse CI
|
|
788
|
+
on: [pull_request]
|
|
789
|
+
|
|
790
|
+
jobs:
|
|
791
|
+
lighthouse:
|
|
792
|
+
runs-on: ubuntu-latest
|
|
793
|
+
steps:
|
|
794
|
+
- uses: actions/checkout@v4
|
|
795
|
+
- uses: actions/setup-node@v4
|
|
796
|
+
with:
|
|
797
|
+
node-version: 20
|
|
798
|
+
|
|
799
|
+
- name: Install & Build
|
|
800
|
+
run: npm ci && npm run build
|
|
801
|
+
|
|
802
|
+
- name: Run Lighthouse CI
|
|
803
|
+
uses: treosh/lighthouse-ci-action@v12
|
|
804
|
+
with:
|
|
805
|
+
configPath: ./lighthouserc.json
|
|
806
|
+
uploadArtifacts: true
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Lighthouse CI Configuration with Budgets
|
|
810
|
+
|
|
811
|
+
```json
|
|
812
|
+
// lighthouserc.json
|
|
813
|
+
{
|
|
814
|
+
"ci": {
|
|
815
|
+
"collect": {
|
|
816
|
+
"url": [
|
|
817
|
+
"http://localhost:3000/",
|
|
818
|
+
"http://localhost:3000/products",
|
|
819
|
+
"http://localhost:3000/checkout"
|
|
820
|
+
],
|
|
821
|
+
"numberOfRuns": 3,
|
|
822
|
+
"startServerCommand": "npm run start"
|
|
823
|
+
},
|
|
824
|
+
"assert": {
|
|
825
|
+
"assertions": {
|
|
826
|
+
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
|
|
827
|
+
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
|
|
828
|
+
"total-blocking-time": ["error", { "maxNumericValue": 300 }],
|
|
829
|
+
"interactive": ["warn", { "maxNumericValue": 3500 }],
|
|
830
|
+
"categories:performance": ["error", { "minScore": 0.9 }]
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
"upload": {
|
|
834
|
+
"target": "temporary-public-storage"
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Performance Budget File
|
|
841
|
+
|
|
842
|
+
```json
|
|
843
|
+
// budget.json (for Lighthouse --budget-path)
|
|
844
|
+
[
|
|
845
|
+
{
|
|
846
|
+
"path": "/*",
|
|
847
|
+
"timings": [
|
|
848
|
+
{ "metric": "largest-contentful-paint", "budget": 2500 },
|
|
849
|
+
{ "metric": "cumulative-layout-shift", "budget": 0.1 },
|
|
850
|
+
{ "metric": "total-blocking-time", "budget": 300 }
|
|
851
|
+
],
|
|
852
|
+
"resourceSizes": [
|
|
853
|
+
{ "resourceType": "script", "budget": 300 },
|
|
854
|
+
{ "resourceType": "image", "budget": 500 },
|
|
855
|
+
{ "resourceType": "stylesheet", "budget": 100 },
|
|
856
|
+
{ "resourceType": "total", "budget": 1000 }
|
|
857
|
+
],
|
|
858
|
+
"resourceCounts": [
|
|
859
|
+
{ "resourceType": "third-party", "budget": 10 },
|
|
860
|
+
{ "resourceType": "script", "budget": 15 }
|
|
861
|
+
]
|
|
862
|
+
}
|
|
863
|
+
]
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### CrUX API Monitoring Script
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
// scripts/check-cwv.mjs — Run daily in CI or cron
|
|
870
|
+
const API_KEY = process.env.CRUX_API_KEY;
|
|
871
|
+
const ORIGIN = 'https://yoursite.com';
|
|
872
|
+
|
|
873
|
+
async function checkCWV() {
|
|
874
|
+
const res = await fetch(
|
|
875
|
+
`https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${API_KEY}`,
|
|
876
|
+
{
|
|
877
|
+
method: 'POST',
|
|
878
|
+
body: JSON.stringify({ origin: ORIGIN, formFactor: 'PHONE' }),
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
const data = await res.json();
|
|
882
|
+
const metrics = data.record.metrics;
|
|
883
|
+
|
|
884
|
+
const checks = [
|
|
885
|
+
{ name: 'LCP', value: metrics.largest_contentful_paint.percentiles.p75, threshold: 2500 },
|
|
886
|
+
{ name: 'INP', value: metrics.interaction_to_next_paint.percentiles.p75, threshold: 200 },
|
|
887
|
+
{ name: 'CLS', value: metrics.cumulative_layout_shift.percentiles.p75, threshold: 0.1 },
|
|
888
|
+
];
|
|
889
|
+
|
|
890
|
+
let failed = false;
|
|
891
|
+
for (const check of checks) {
|
|
892
|
+
const status = check.value <= check.threshold ? 'PASS' : 'FAIL';
|
|
893
|
+
console.log(`${check.name}: ${check.value} (threshold: ${check.threshold}) [${status}]`);
|
|
894
|
+
if (status === 'FAIL') failed = true;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (failed) {
|
|
898
|
+
// Send alert via Slack/PagerDuty/email
|
|
899
|
+
console.error('Core Web Vitals regression detected!');
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
checkCWV();
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Automated Regression Alerts
|
|
908
|
+
|
|
909
|
+
Set up monitoring at three levels:
|
|
910
|
+
1. **CI/CD (Lab):** Lighthouse CI on every PR — catches regressions before merge
|
|
911
|
+
2. **Daily CrUX check (Field):** Script above — catches real-user regressions within 28 days
|
|
912
|
+
3. **Real-time RUM (Field):** web-vitals library sending to your analytics — catches regressions within minutes
|
|
913
|
+
|
|
914
|
+
**Alert thresholds recommendation:**
|
|
915
|
+
- Warn when p75 exceeds 80% of the "Good" threshold (LCP > 2.0s, INP > 160ms, CLS > 0.08)
|
|
916
|
+
- Alert/block when p75 exceeds the "Good" threshold (LCP > 2.5s, INP > 200ms, CLS > 0.1)
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
## Decision Trees
|
|
921
|
+
|
|
922
|
+
### "My LCP is Slow" Decision Tree
|
|
923
|
+
|
|
924
|
+
```
|
|
925
|
+
LCP > 2.5s?
|
|
926
|
+
├── Check TTFB first (DevTools → Network → document request)
|
|
927
|
+
│ ├── TTFB > 800ms → Server-side issue
|
|
928
|
+
│ │ ├── Slow database queries → Add caching layer (Redis/CDN)
|
|
929
|
+
│ │ ├── No CDN → Add CDN (reduces TTFB by 100-500ms)
|
|
930
|
+
│ │ └── Dynamic page on every request → Switch to SSG or ISR
|
|
931
|
+
│ └── TTFB < 800ms → Client-side issue
|
|
932
|
+
│ ├── What is the LCP element? (DevTools → Performance → LCP marker)
|
|
933
|
+
│ │ ├── Image
|
|
934
|
+
│ │ │ ├── Is it lazy-loaded? → Remove loading="lazy"
|
|
935
|
+
│ │ │ ├── Is it in CSS background-image? → Move to <img> tag + preload
|
|
936
|
+
│ │ │ ├── Is fetchpriority="high" set? → Add it
|
|
937
|
+
│ │ │ ├── Is image > 200KB? → Convert to WebP/AVIF, resize
|
|
938
|
+
│ │ │ └── Is image preloaded? → Add <link rel="preload">
|
|
939
|
+
│ │ ├── Text block
|
|
940
|
+
│ │ │ ├── Font blocking render? → Add font-display: optional
|
|
941
|
+
│ │ │ ├── CSS render-blocking? → Inline critical CSS
|
|
942
|
+
│ │ │ └── JS rendering content? → Switch to SSR
|
|
943
|
+
│ │ └── Video
|
|
944
|
+
│ │ ├── Add poster attribute with optimized image
|
|
945
|
+
│ │ └── Preload poster image
|
|
946
|
+
│ └── Check render-blocking resources (Lighthouse audit)
|
|
947
|
+
│ ├── Blocking CSS > 50KB → Extract + inline critical CSS
|
|
948
|
+
│ ├── Blocking JS in <head> → Add defer attribute
|
|
949
|
+
│ └── Third-party scripts blocking → Load async or defer to after LCP
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
### "I Have Layout Shifts" Decision Tree
|
|
953
|
+
|
|
954
|
+
```
|
|
955
|
+
CLS > 0.1?
|
|
956
|
+
├── Identify shifting elements (DevTools → Performance → Layout Shift clusters)
|
|
957
|
+
│ ├── Images/videos shifting?
|
|
958
|
+
│ │ ├── Missing width/height attributes → Add explicit dimensions
|
|
959
|
+
│ │ ├── Responsive but no aspect-ratio → Add CSS aspect-ratio
|
|
960
|
+
│ │ └── Container resizing → Set min-height on container
|
|
961
|
+
│ ├── Text shifting (font swap)?
|
|
962
|
+
│ │ ├── Large CLS from font → Use font-display: optional
|
|
963
|
+
│ │ ├── Moderate CLS from font → Use size-adjust on fallback
|
|
964
|
+
│ │ └── Slow font load → Preload critical font files
|
|
965
|
+
│ ├── Ad/embed shifting?
|
|
966
|
+
│ │ ├── No reserved space → Add min-height to ad container
|
|
967
|
+
│ │ ├── Ad sizes vary → Use the most common ad size as min-height
|
|
968
|
+
│ │ └── Consider sticky/overlay ad formats (no layout shift)
|
|
969
|
+
│ ├── Dynamically injected content?
|
|
970
|
+
│ │ ├── Cookie banner → Use position: fixed (no CLS)
|
|
971
|
+
│ │ ├── Notification bar → Reserve space with min-height
|
|
972
|
+
│ │ └── Lazy-loaded components → Use skeleton with matching dimensions
|
|
973
|
+
│ └── CSS animations?
|
|
974
|
+
│ ├── Using top/left/margin → Switch to transform: translate()
|
|
975
|
+
│ └── Using width/height → Switch to transform: scale()
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### "INP is High" Decision Tree
|
|
979
|
+
|
|
980
|
+
```
|
|
981
|
+
INP > 200ms?
|
|
982
|
+
├── Which phase is slow? (web-vitals attribution build)
|
|
983
|
+
│ ├── Input Delay > 100ms (waiting for main thread)
|
|
984
|
+
│ │ ├── Long tasks visible? (DevTools → Performance → Long Tasks)
|
|
985
|
+
│ │ │ ├── Third-party scripts → Defer to after interaction, use Partytown
|
|
986
|
+
│ │ │ ├── Heavy initialization → Use requestIdleCallback or lazy init
|
|
987
|
+
│ │ │ └── Large JS bundle → Code-split, tree-shake, reduce bundle
|
|
988
|
+
│ │ └── Frequent timer/interval callbacks → Reduce frequency, use rAF
|
|
989
|
+
│ ├── Processing Duration > 100ms (event handler too slow)
|
|
990
|
+
│ │ ├── Layout thrashing? → Batch DOM reads before writes
|
|
991
|
+
│ │ ├── Expensive computation? → Move to Web Worker
|
|
992
|
+
│ │ ├── Large React re-render? → Use memo/useMemo, virtualize lists
|
|
993
|
+
│ │ └── Synchronous API calls? → Should never block event handler
|
|
994
|
+
│ └── Presentation Delay > 100ms (rendering after handler)
|
|
995
|
+
│ ├── Large DOM (>1500 nodes) → Reduce DOM size, virtualize
|
|
996
|
+
│ ├── Complex CSS selectors → Simplify, use BEM/utility classes
|
|
997
|
+
│ └── Many style recalculations → Use CSS containment (contain: layout)
|
|
998
|
+
├── Is it a specific interaction type?
|
|
999
|
+
│ ├── Click handlers → Check for synchronous work in handler
|
|
1000
|
+
│ ├── Key press handlers → Check for synchronous search/filter
|
|
1001
|
+
│ └── Scroll-triggered → Use passive listeners, throttle with rAF
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
## Code Examples
|
|
1007
|
+
|
|
1008
|
+
### 1. Complete Web Vitals Monitoring Setup
|
|
1009
|
+
|
|
1010
|
+
```javascript
|
|
1011
|
+
// lib/vitals.js — Production-ready CWV monitoring
|
|
1012
|
+
import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
|
|
1013
|
+
|
|
1014
|
+
const ANALYTICS_ENDPOINT = '/api/vitals';
|
|
1015
|
+
const queue = [];
|
|
1016
|
+
let flushTimer = null;
|
|
1017
|
+
|
|
1018
|
+
function enqueue(metric) {
|
|
1019
|
+
queue.push({
|
|
1020
|
+
name: metric.name,
|
|
1021
|
+
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
|
|
1022
|
+
rating: metric.rating,
|
|
1023
|
+
delta: metric.delta,
|
|
1024
|
+
id: metric.id,
|
|
1025
|
+
page: window.location.pathname,
|
|
1026
|
+
navigationType: metric.navigationType,
|
|
1027
|
+
// Attribution data for debugging
|
|
1028
|
+
...(metric.attribution && {
|
|
1029
|
+
debug: getDebugInfo(metric),
|
|
1030
|
+
}),
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// Batch send: flush after 5 seconds or 10 metrics
|
|
1034
|
+
clearTimeout(flushTimer);
|
|
1035
|
+
if (queue.length >= 10) {
|
|
1036
|
+
flush();
|
|
1037
|
+
} else {
|
|
1038
|
+
flushTimer = setTimeout(flush, 5000);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function getDebugInfo(metric) {
|
|
1043
|
+
if (metric.name === 'LCP') {
|
|
1044
|
+
return {
|
|
1045
|
+
element: metric.attribution.element,
|
|
1046
|
+
url: metric.attribution.url,
|
|
1047
|
+
ttfb: metric.attribution.timeToFirstByte,
|
|
1048
|
+
loadDelay: metric.attribution.resourceLoadDelay,
|
|
1049
|
+
loadTime: metric.attribution.resourceLoadTime,
|
|
1050
|
+
renderDelay: metric.attribution.elementRenderDelay,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (metric.name === 'INP') {
|
|
1054
|
+
return {
|
|
1055
|
+
eventType: metric.attribution.interactionType,
|
|
1056
|
+
target: metric.attribution.interactionTarget,
|
|
1057
|
+
inputDelay: metric.attribution.inputDelay,
|
|
1058
|
+
processingTime: metric.attribution.processingDuration,
|
|
1059
|
+
presentationDelay: metric.attribution.presentationDelay,
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if (metric.name === 'CLS') {
|
|
1063
|
+
return {
|
|
1064
|
+
largestTarget: metric.attribution.largestShiftTarget,
|
|
1065
|
+
largestValue: metric.attribution.largestShiftValue,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function flush() {
|
|
1071
|
+
if (queue.length === 0) return;
|
|
1072
|
+
const body = JSON.stringify(queue.splice(0));
|
|
1073
|
+
if (navigator.sendBeacon) {
|
|
1074
|
+
navigator.sendBeacon(ANALYTICS_ENDPOINT, body);
|
|
1075
|
+
} else {
|
|
1076
|
+
fetch(ANALYTICS_ENDPOINT, { body, method: 'POST', keepalive: true });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Register listeners — call ONCE per page load
|
|
1081
|
+
onLCP(enqueue);
|
|
1082
|
+
onINP(enqueue);
|
|
1083
|
+
onCLS(enqueue);
|
|
1084
|
+
|
|
1085
|
+
// Ensure final flush on page hide
|
|
1086
|
+
document.addEventListener('visibilitychange', () => {
|
|
1087
|
+
if (document.visibilityState === 'hidden') flush();
|
|
1088
|
+
});
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
### 2. LCP Preload with Dynamic Detection
|
|
1092
|
+
|
|
1093
|
+
```javascript
|
|
1094
|
+
// Detect and preload the LCP image dynamically based on viewport
|
|
1095
|
+
// Place in <head> as inline script for earliest execution
|
|
1096
|
+
(function() {
|
|
1097
|
+
const mq = window.matchMedia('(max-width: 768px)');
|
|
1098
|
+
const link = document.createElement('link');
|
|
1099
|
+
link.rel = 'preload';
|
|
1100
|
+
link.as = 'image';
|
|
1101
|
+
link.fetchPriority = 'high';
|
|
1102
|
+
|
|
1103
|
+
if (mq.matches) {
|
|
1104
|
+
link.href = '/images/hero-mobile.webp';
|
|
1105
|
+
link.type = 'image/webp';
|
|
1106
|
+
} else {
|
|
1107
|
+
link.href = '/images/hero-desktop.webp';
|
|
1108
|
+
link.type = 'image/webp';
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
document.head.appendChild(link);
|
|
1112
|
+
})();
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
### 3. Skeleton Screen That Prevents CLS
|
|
1116
|
+
|
|
1117
|
+
```html
|
|
1118
|
+
<!-- Skeleton matches final content dimensions exactly -->
|
|
1119
|
+
<div class="product-card" style="width: 300px; min-height: 420px;">
|
|
1120
|
+
<div class="skeleton-image" style="aspect-ratio: 4/3; background: #e0e0e0;"></div>
|
|
1121
|
+
<div class="skeleton-text" style="height: 24px; margin: 12px 0; background: #e0e0e0; border-radius: 4px;"></div>
|
|
1122
|
+
<div class="skeleton-text" style="height: 16px; width: 60%; background: #e0e0e0; border-radius: 4px;"></div>
|
|
1123
|
+
<div class="skeleton-price" style="height: 28px; width: 40%; margin-top: 8px; background: #e0e0e0; border-radius: 4px;"></div>
|
|
1124
|
+
</div>
|
|
1125
|
+
|
|
1126
|
+
<style>
|
|
1127
|
+
.product-card [class^="skeleton-"] {
|
|
1128
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
1129
|
+
}
|
|
1130
|
+
@keyframes pulse {
|
|
1131
|
+
0%, 100% { opacity: 1; }
|
|
1132
|
+
50% { opacity: 0.5; }
|
|
1133
|
+
}
|
|
1134
|
+
</style>
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
### 4. Yielding Pattern with Time-Slicing for INP
|
|
1138
|
+
|
|
1139
|
+
```javascript
|
|
1140
|
+
// Time-based yielding — more adaptive than fixed batch sizes
|
|
1141
|
+
async function processItems(items, processItem, maxBlockTime = 50) {
|
|
1142
|
+
let deadline = performance.now() + maxBlockTime;
|
|
1143
|
+
|
|
1144
|
+
for (let i = 0; i < items.length; i++) {
|
|
1145
|
+
processItem(items[i]);
|
|
1146
|
+
|
|
1147
|
+
if (performance.now() >= deadline) {
|
|
1148
|
+
// Yield to main thread for pending interactions
|
|
1149
|
+
await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
|
|
1150
|
+
deadline = performance.now() + maxBlockTime;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Usage
|
|
1156
|
+
await processItems(
|
|
1157
|
+
thousandProducts,
|
|
1158
|
+
(product) => renderProductCard(product),
|
|
1159
|
+
50 // yield every 50ms to stay under long-task threshold
|
|
1160
|
+
);
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
### 5. Responsive Image Component (React)
|
|
1164
|
+
|
|
1165
|
+
```jsx
|
|
1166
|
+
// components/OptimizedImage.jsx
|
|
1167
|
+
function OptimizedImage({
|
|
1168
|
+
src,
|
|
1169
|
+
alt,
|
|
1170
|
+
width,
|
|
1171
|
+
height,
|
|
1172
|
+
isLCP = false,
|
|
1173
|
+
sizes = '100vw',
|
|
1174
|
+
}) {
|
|
1175
|
+
// Generate srcset for multiple widths
|
|
1176
|
+
const widths = [400, 800, 1200, 1600];
|
|
1177
|
+
const avifSrcSet = widths.map(w => `${src}?format=avif&w=${w} ${w}w`).join(', ');
|
|
1178
|
+
const webpSrcSet = widths.map(w => `${src}?format=webp&w=${w} ${w}w`).join(', ');
|
|
1179
|
+
|
|
1180
|
+
return (
|
|
1181
|
+
<picture>
|
|
1182
|
+
<source srcSet={avifSrcSet} sizes={sizes} type="image/avif" />
|
|
1183
|
+
<source srcSet={webpSrcSet} sizes={sizes} type="image/webp" />
|
|
1184
|
+
<img
|
|
1185
|
+
src={`${src}?format=webp&w=800`}
|
|
1186
|
+
alt={alt}
|
|
1187
|
+
width={width}
|
|
1188
|
+
height={height}
|
|
1189
|
+
sizes={sizes}
|
|
1190
|
+
loading={isLCP ? 'eager' : 'lazy'}
|
|
1191
|
+
decoding={isLCP ? 'sync' : 'async'}
|
|
1192
|
+
fetchPriority={isLCP ? 'high' : 'auto'}
|
|
1193
|
+
style={{ width: '100%', height: 'auto', aspectRatio: `${width}/${height}` }}
|
|
1194
|
+
/>
|
|
1195
|
+
</picture>
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Usage
|
|
1200
|
+
<OptimizedImage
|
|
1201
|
+
src="/api/images/hero"
|
|
1202
|
+
alt="Product showcase"
|
|
1203
|
+
width={1200}
|
|
1204
|
+
height={600}
|
|
1205
|
+
isLCP={true}
|
|
1206
|
+
sizes="(max-width: 768px) 100vw, 1200px"
|
|
1207
|
+
/>
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### 6. Third-Party Script Loading Strategy
|
|
1211
|
+
|
|
1212
|
+
```html
|
|
1213
|
+
<!-- Priority 1: Critical (inline in <head>) -->
|
|
1214
|
+
<!-- Only truly critical CSS and no JS in head -->
|
|
1215
|
+
|
|
1216
|
+
<!-- Priority 2: High (async, in <head>) — needed for page functionality -->
|
|
1217
|
+
<script async src="/js/app.bundle.js"></script>
|
|
1218
|
+
|
|
1219
|
+
<!-- Priority 3: Medium (defer) — needed but not for initial render -->
|
|
1220
|
+
<script defer src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>
|
|
1221
|
+
|
|
1222
|
+
<!-- Priority 4: Low — load after page is interactive -->
|
|
1223
|
+
<script>
|
|
1224
|
+
// Load non-critical third-party scripts after page load
|
|
1225
|
+
window.addEventListener('load', () => {
|
|
1226
|
+
setTimeout(() => {
|
|
1227
|
+
// Chat widget
|
|
1228
|
+
const chat = document.createElement('script');
|
|
1229
|
+
chat.src = 'https://widget.chat-service.com/loader.js';
|
|
1230
|
+
chat.async = true;
|
|
1231
|
+
document.body.appendChild(chat);
|
|
1232
|
+
|
|
1233
|
+
// Social sharing buttons
|
|
1234
|
+
const social = document.createElement('script');
|
|
1235
|
+
social.src = 'https://platform.twitter.com/widgets.js';
|
|
1236
|
+
social.async = true;
|
|
1237
|
+
document.body.appendChild(social);
|
|
1238
|
+
}, 3000); // 3 second delay after load
|
|
1239
|
+
});
|
|
1240
|
+
</script>
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
### 7. Next.js App Router Configuration for Optimal CWV
|
|
1244
|
+
|
|
1245
|
+
```tsx
|
|
1246
|
+
// app/layout.tsx — Root layout with CWV optimizations
|
|
1247
|
+
import { Inter } from 'next/font/google';
|
|
1248
|
+
|
|
1249
|
+
// Next.js automatically optimizes Google Fonts:
|
|
1250
|
+
// - Self-hosts the font files (no external request)
|
|
1251
|
+
// - Generates size-adjust CSS for zero-CLS font loading
|
|
1252
|
+
const inter = Inter({
|
|
1253
|
+
subsets: ['latin'],
|
|
1254
|
+
display: 'swap', // Show text immediately
|
|
1255
|
+
preload: true, // Preload font files
|
|
1256
|
+
fallback: ['system-ui'], // Fallback while loading
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
export default function RootLayout({ children }) {
|
|
1260
|
+
return (
|
|
1261
|
+
<html lang="en" className={inter.className}>
|
|
1262
|
+
<head>
|
|
1263
|
+
{/* Preconnect to critical third-party origins */}
|
|
1264
|
+
<link rel="preconnect" href="https://cdn.yoursite.com" />
|
|
1265
|
+
<link rel="dns-prefetch" href="https://analytics.yoursite.com" />
|
|
1266
|
+
</head>
|
|
1267
|
+
<body>{children}</body>
|
|
1268
|
+
</html>
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// app/page.tsx — Page with optimized LCP
|
|
1273
|
+
import Image from 'next/image';
|
|
1274
|
+
|
|
1275
|
+
export default function HomePage() {
|
|
1276
|
+
return (
|
|
1277
|
+
<main>
|
|
1278
|
+
{/* Next.js Image component handles:
|
|
1279
|
+
- Automatic WebP/AVIF format negotiation
|
|
1280
|
+
- Responsive srcset generation
|
|
1281
|
+
- Lazy loading (except when priority=true)
|
|
1282
|
+
- Width/height for CLS prevention */}
|
|
1283
|
+
<Image
|
|
1284
|
+
src="/hero.jpg"
|
|
1285
|
+
alt="Hero"
|
|
1286
|
+
width={1200}
|
|
1287
|
+
height={600}
|
|
1288
|
+
priority // Sets fetchpriority="high" + no lazy loading
|
|
1289
|
+
sizes="100vw"
|
|
1290
|
+
quality={80}
|
|
1291
|
+
/>
|
|
1292
|
+
</main>
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
### 8. Layout Shift Debugging Overlay
|
|
1298
|
+
|
|
1299
|
+
```javascript
|
|
1300
|
+
// Debug tool: Visualize layout shifts in real-time during development
|
|
1301
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1302
|
+
const observer = new PerformanceObserver((list) => {
|
|
1303
|
+
for (const entry of list.getEntries()) {
|
|
1304
|
+
if (!entry.hadRecentInput) { // Only unexpected shifts
|
|
1305
|
+
for (const source of entry.sources || []) {
|
|
1306
|
+
const el = source.node;
|
|
1307
|
+
if (el) {
|
|
1308
|
+
// Highlight shifting element with red border
|
|
1309
|
+
el.style.outline = '3px solid red';
|
|
1310
|
+
el.setAttribute('data-cls-value', entry.value.toFixed(4));
|
|
1311
|
+
console.warn('Layout shift detected:', {
|
|
1312
|
+
element: el,
|
|
1313
|
+
value: entry.value.toFixed(4),
|
|
1314
|
+
previousRect: source.previousRect,
|
|
1315
|
+
currentRect: source.currentRect,
|
|
1316
|
+
});
|
|
1317
|
+
// Remove highlight after 2 seconds
|
|
1318
|
+
setTimeout(() => { el.style.outline = ''; }, 2000);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
observer.observe({ type: 'layout-shift', buffered: true });
|
|
1325
|
+
}
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
---
|
|
1329
|
+
|
|
1330
|
+
## Quick Reference
|
|
1331
|
+
|
|
1332
|
+
### Metric Thresholds
|
|
1333
|
+
|
|
1334
|
+
| Metric | Good | Acceptable | Needs Work | Percentile Used |
|
|
1335
|
+
|--------|------|------------|------------|----------------|
|
|
1336
|
+
| **LCP** | < 2.5s | 2.5s - 4.0s | > 4.0s | 75th (p75) |
|
|
1337
|
+
| **INP** | < 200ms | 200ms - 500ms | > 500ms | 75th (p75) |
|
|
1338
|
+
| **CLS** | < 0.1 | 0.1 - 0.25 | > 0.25 | 75th (p75) |
|
|
1339
|
+
|
|
1340
|
+
### Top 3 Fixes Per Metric
|
|
1341
|
+
|
|
1342
|
+
| Metric | Fix #1 | Fix #2 | Fix #3 |
|
|
1343
|
+
|--------|--------|--------|--------|
|
|
1344
|
+
| **LCP** | Add `fetchpriority="high"` to LCP image (~700ms gain) | Use WebP/AVIF images (50-70% size reduction) | Inline critical CSS (300-800ms gain on 3G) |
|
|
1345
|
+
| **INP** | Break long tasks with `scheduler.yield()` | Move computation to Web Workers | Use `content-visibility: auto` for off-screen DOM |
|
|
1346
|
+
| **CLS** | Add `width`/`height` to all images (fixes ~60% of CLS) | Use `font-display: optional` or `size-adjust` | Reserve space for ads with `min-height` |
|
|
1347
|
+
|
|
1348
|
+
### Measurement Cheat Sheet
|
|
1349
|
+
|
|
1350
|
+
| Need | Tool | Command / URL |
|
|
1351
|
+
|------|------|--------------|
|
|
1352
|
+
| Quick check | PageSpeed Insights | pagespeed.web.dev |
|
|
1353
|
+
| Lab audit | Lighthouse CLI | `npx lighthouse https://url --output html` |
|
|
1354
|
+
| Field data (per-URL) | CrUX API | `POST chromeuxreport.googleapis.com/v1/records:queryRecord` |
|
|
1355
|
+
| Field data (bulk) | BigQuery | `SELECT * FROM chrome-ux-report.all.YYYYMM` |
|
|
1356
|
+
| CI/CD gate | Lighthouse CI | `npx @lhci/cli autorun --config=lighthouserc.json` |
|
|
1357
|
+
| Real-time RUM | web-vitals library | `npm install web-vitals` |
|
|
1358
|
+
| Debug shifts | DevTools | Performance tab > "Layout Shifts" track |
|
|
1359
|
+
| Debug INP | DevTools | Performance tab > "Interactions" track |
|
|
1360
|
+
|
|
1361
|
+
### Resource Size Budgets
|
|
1362
|
+
|
|
1363
|
+
| Resource Type | Recommended Budget | Why |
|
|
1364
|
+
|--------------|-------------------|-----|
|
|
1365
|
+
| Total page weight | < 1MB (mobile), < 2MB (desktop) | 1MB on 3G takes ~5s to download |
|
|
1366
|
+
| JavaScript (total) | < 300KB compressed | JS is byte-for-byte the most expensive resource |
|
|
1367
|
+
| CSS (total) | < 100KB compressed | Render-blocking; affects LCP directly |
|
|
1368
|
+
| LCP image | < 200KB | Must load within the 2.5s budget |
|
|
1369
|
+
| Fonts | < 100KB total | Each font file adds a blocking request |
|
|
1370
|
+
| Third-party scripts | < 10 scripts, < 200KB total | Each script competes for main thread |
|
|
1371
|
+
|
|
1372
|
+
---
|
|
1373
|
+
|
|
1374
|
+
*Researched: 2026-03-08 | Sources: [web.dev/articles/lcp](https://web.dev/articles/lcp), [web.dev/articles/inp](https://web.dev/articles/inp), [web.dev/articles/cls](https://web.dev/articles/cls), [web.dev/articles/optimize-lcp](https://web.dev/articles/optimize-lcp), [web.dev/case-studies/vodafone](https://web.dev/case-studies/vodafone), [web.dev/case-studies/vitals-business-impact](https://web.dev/case-studies/vitals-business-impact), [web.dev/articles/fetch-priority](https://web.dev/articles/fetch-priority), [web.dev/articles/optimize-long-tasks](https://web.dev/articles/optimize-long-tasks), [developer.chrome.com/docs/crux](https://developer.chrome.com/docs/crux/guides/crux-api), [github.com/GoogleChrome/web-vitals](https://github.com/GoogleChrome/web-vitals), [addyosmani.com/blog/fetch-priority](https://addyosmani.com/blog/fetch-priority/), [developer.mozilla.org/en-US/blog/fix-image-lcp](https://developer.mozilla.org/en-US/blog/fix-image-lcp/), [nitropack.io/blog/interaction-to-next-paint-inp](https://nitropack.io/blog/interaction-to-next-paint-inp/), [developer.chrome.com/blog/use-scheduler-yield](https://developer.chrome.com/blog/use-scheduler-yield), [HTTP Archive 2025 Web Almanac](https://almanac.httparchive.org)*
|