<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Addy Osmani</title>
    <description>The latest articles on Forem by Addy Osmani (@addyosmani).</description>
    <link>https://forem.com/addyosmani</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F5259%2F20171596-935a-4842-8f1d-387bcbe8c3bb.png</url>
      <title>Forem: Addy Osmani</title>
      <link>https://forem.com/addyosmani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/addyosmani"/>
    <language>en</language>
    <item>
      <title>How to write a good spec for AI agents</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/addyosmani/how-to-write-a-good-spec-for-ai-agents-31i9</link>
      <guid>https://forem.com/addyosmani/how-to-write-a-good-spec-for-ai-agents-31i9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR: Aim for a clear spec covering just enough nuance (this may include structure, style, testing, boundaries) to guide the AI without overwhelming it. Break large tasks into smaller ones vs. keeping everything in one large prompt. Plan first in read-only mode, then execute and iterate continuously.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"I've heard a lot about writing good specs for AI agents, but haven't found a solid framework yet. I could write a spec that rivals an RFC, but at some point the context is too large and the model breaks down."&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Many developers share this frustration. Simply throwing a massive spec at an AI agent doesn't work - context window limits and the model's "attention budget" get in the way. The key is to write smart specs: documents that guide the agent clearly, stay within practical context sizes, and evolve with the project. This guide distills best practices from my use of coding agents including Claude Code and Gemini CLI into a framework for spec-writing that keeps your AI agents focused and productive.&lt;/p&gt;

&lt;p&gt;We'll cover five principles for great AI agent specs, each starting with a bolded takeaway. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Start with a high-level vision and let the AI draft the details&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Kick off your project with a concise high-level spec, then have the AI expand it into a detailed plan.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Instead of over-engineering upfront, begin with a clear goal statement and a few core requirements. Treat this as a "product brief" and let the agent generate a more elaborate spec from it. This leverages the AI's strength in elaboration while you maintain control of the direction. This works well unless you already feel you have very specific technical requirements that must be met from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; LLM-based agents excel at fleshing out details when given a solid high-level directive, but they need a clear mission to avoid drifting off course. By providing a short outline or objective description and asking the AI to produce a full specification (e.g. a spec.md), you create a persistent reference for the agent. Planning in advance matters even more with an agent - you can iterate on the plan first, then hand it off to the agent to write the code. The spec becomes the first artifact you and the AI build together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical approach:&lt;/strong&gt; Start a new coding session by prompting, "You are an AI software engineer. Draft a detailed specification for [project X] covering objectives, features, constraints, and a step-by-step plan." Keep your initial prompt high-level - e.g. "Build a web app where users can track tasks (to-do list), with user accounts, a database, and a simple UI". The agent might respond with a structured draft spec: an overview, feature list, tech stack suggestions, data model, and so on. This spec then becomes the "source of truth" that both you and the agent can refer back to. GitHub's AI team promotes &lt;a href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/" rel="noopener noreferrer"&gt;spec-driven development&lt;/a&gt; where "specs become the shared source of truth… living, executable artifacts that evolve with the project". Before writing any code, review and refine the AI's spec. Make sure it aligns with your vision and correct any hallucinations or off-target details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Plan Mode to enforce planning-first:&lt;/strong&gt; Tools like Claude Code offer a &lt;a href="https://code.claude.com/docs/en/common-workflows" rel="noopener noreferrer"&gt;Plan Mode&lt;/a&gt; that restricts the agent to read-only operations - it can analyze your codebase and create detailed plans but won't write any code until you're ready. This is ideal for the planning phase: start in Plan Mode (Shift+Tab in Claude Code), describe what you want to build, and let the agent draft a spec while exploring your existing code. Ask it to clarify ambiguities by questioning you about the plan. Have it review the plan for architecture, best practices, security risks, and testing strategy. The goal is to refine the plan until there's no room for misinterpretation. Only then do you exit Plan Mode and let the agent execute. This workflow prevents the common trap of jumping straight into code generation before the spec is solid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the spec as context:&lt;/strong&gt; Once approved, save this spec (e.g. as SPEC.md) and feed relevant sections into the agent as needed. Many developers using a strong model do exactly this - the spec file persists between sessions, anchoring the AI whenever work resumes on the project. This mitigates the forgetfulness that can happen when the conversation history gets too long or when you have to restart an agent. It's akin to how one would use a Product Requirements Document (PRD) in a team: a reference that everyone (human or AI) can consult to stay on track. Experienced folks often "&lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;write good documentation first&lt;/a&gt; and the model may be able to build the matching implementation from that input alone" as one engineer observed. The spec is that documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep it goal-oriented:&lt;/strong&gt; A high-level spec for an AI agent should focus on what and why, more than the nitty-gritty how (at least initially). Think of it like the user story and acceptance criteria: Who is the user? What do they need? What does success look like? (e.g. "User can add, edit, complete tasks; data is saved persistently; the app is responsive and secure"). This keeps the AI's detailed spec grounded in user needs and outcome, not just technical to-dos. As the &lt;a href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/" rel="noopener noreferrer"&gt;GitHub Spec Kit docs&lt;/a&gt; put it, provide a high-level description of what you're building and why, and let the coding agent generate a detailed specification focusing on user experience and success criteria. Starting with this big-picture vision prevents the agent from losing sight of the forest for the trees when it later gets into coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Structure the spec like a professional PRD (or SRS)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treat your AI spec as a structured document (PRD) with clear sections, not a loose pile of notes.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Many developers treat specs for agents much like traditional Product Requirement Documents (PRDs) or System Design docs - comprehensive, well-organized, and easy for a "literal-minded" AI to parse. This formal approach gives the agent a blueprint to follow and reduces ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The six core areas:&lt;/strong&gt; GitHub's analysis of &lt;a href="https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/" rel="noopener noreferrer"&gt;over 2,500 agent configuration files&lt;/a&gt; revealed a clear pattern: the most effective specs cover six areas. Use this as a checklist for completeness:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Commands:&lt;/strong&gt; Put executable commands early - not just tool names, but full commands with flags: &lt;code&gt;npm test&lt;/code&gt;, &lt;code&gt;pytest -v&lt;/code&gt;, &lt;code&gt;npm run build&lt;/code&gt;. The agent will reference these constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Testing:&lt;/strong&gt; How to run tests, what framework you use, where test files live, and what coverage expectations exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Project structure:&lt;/strong&gt; Where source code lives, where tests go, where docs belong. Be explicit: "&lt;code&gt;src/&lt;/code&gt; for application code, &lt;code&gt;tests/&lt;/code&gt; for unit tests, &lt;code&gt;docs/&lt;/code&gt; for documentation."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Code style:&lt;/strong&gt; One real code snippet showing your style beats three paragraphs describing it. Include naming conventions, formatting rules, and examples of good output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Git workflow:&lt;/strong&gt; Branch naming, commit message format, PR requirements. The agent can follow these if you spell them out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Boundaries:&lt;/strong&gt; What the agent should never touch - secrets, vendor directories, production configs, specific folders. "Never commit secrets" was the single most common helpful constraint in the GitHub study.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be specific about your stack:&lt;/strong&gt; Say "React 18 with TypeScript, Vite, and Tailwind CSS" not "React project." Include versions and key dependencies. Vague specs produce vague code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a consistent format:&lt;/strong&gt; Clarity is king. Many devs use Markdown headings or even XML-like tags in the spec to delineate sections, because AI models handle well-structured text better than free-form prose. For example, you might structure the spec as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Spec: My team's tasks app&lt;/span&gt;

&lt;span class="gu"&gt;## Objective&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Build a web app for small teams to manage tasks...

&lt;span class="gu"&gt;## Tech Stack&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; React 18+, TypeScript, Vite, Tailwind CSS
&lt;span class="p"&gt;-&lt;/span&gt; Node.js/Express backend, PostgreSQL, Prisma ORM

&lt;span class="gu"&gt;## Commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Build: &lt;span class="sb"&gt;`npm run build`&lt;/span&gt; (compiles TypeScript, outputs to dist/)
&lt;span class="p"&gt;-&lt;/span&gt; Test: &lt;span class="sb"&gt;`npm test`&lt;/span&gt; (runs Jest, must pass before commits)
&lt;span class="p"&gt;-&lt;/span&gt; Lint: &lt;span class="sb"&gt;`npm run lint --fix`&lt;/span&gt; (auto-fixes ESLint errors)

&lt;span class="gu"&gt;## Project Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`src/`&lt;/span&gt; – Application source code
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`tests/`&lt;/span&gt; – Unit and integration tests
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`docs/`&lt;/span&gt; – Documentation

&lt;span class="gu"&gt;## Boundaries&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ✅ Always: Run tests before commits, follow naming conventions
&lt;span class="p"&gt;-&lt;/span&gt; ⚠️ Ask first: Database schema changes, adding dependencies
&lt;span class="p"&gt;-&lt;/span&gt; 🚫 Never: Commit secrets, edit node_modules/, modify CI config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This level of organization not only helps you think clearly, it helps the AI find information. Anthropic engineers recommend &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;organizing prompts into distinct sections&lt;/a&gt; (like &amp;lt;background&amp;gt;, &amp;lt;instructions&amp;gt;, &amp;lt;tools&amp;gt;, &amp;lt;output_format&amp;gt; etc.) for exactly this reason - it gives the model strong cues about which info is which. And remember, "minimal does not necessarily mean short" - don't shy away from detail in the spec if it matters, but keep it focused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrate specs into your toolchain:&lt;/strong&gt; Treat specs as "executable artifacts" tied to version control and CI/CD. The &lt;a href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/" rel="noopener noreferrer"&gt;GitHub Spec Kit&lt;/a&gt; uses a four-phase, gated workflow that makes your specification the center of your engineering process. Instead of writing a spec and setting it aside, the spec drives the implementation, checklists, and task breakdowns. Your primary role is to steer; the coding agent does the bulk of the writing. Each phase has a specific job, and you don't move to the next one until the current task is fully validated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fspec-driven-development.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fspec-driven-development.jpg" alt="Spec Driven Development Workflow" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Specify:&lt;/strong&gt; You provide a high-level description of what you're building and why, and the coding agent generates a detailed specification. This isn't about technical stacks or app design - it's about user journeys, experiences, and what success looks like. Who will use this? What problem does it solve? How will they interact with it? Think of it as mapping the user experience you want to create, and letting the coding agent flesh out the details. This becomes a living artifact that evolves as you learn more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Plan:&lt;/strong&gt; Now you get technical. You provide your desired stack, architecture, and constraints, and the coding agent generates a comprehensive technical plan. If your company standardizes on certain technologies, this is where you say so. If you're integrating with legacy systems or have compliance requirements, all of that goes here. You can ask for multiple plan variations to compare approaches. If you make internal docs available, the agent can integrate your architectural patterns directly into the plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tasks:&lt;/strong&gt; The coding agent takes the spec and plan and breaks them into actual work - small, reviewable chunks that each solve a specific piece of the puzzle. Each task should be something you can implement and test in isolation, almost like test-driven development for your AI agent. Instead of "build authentication," you get concrete tasks like "create a user registration endpoint that validates email format."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Implement:&lt;/strong&gt; Your coding agent tackles tasks one by one (or in parallel). Instead of reviewing thousand-line code dumps, you review focused changes that solve specific problems. The agent knows what to build (specification), how to build it (plan), and what to work on (task). Crucially, your role is to verify at each phase: Does the spec capture what you want? Does the plan account for constraints? Are there edge cases the AI missed? The process builds in checkpoints for you to critique, spot gaps, and course-correct before moving forward.&lt;/p&gt;

&lt;p&gt;This gated workflow prevents what Willison calls "house of cards code" - fragile AI outputs that collapse under scrutiny. Anthropic's Skills system offers a similar pattern, letting you define reusable Markdown-based behaviors that agents invoke. By embedding your spec in these workflows, you ensure the agent can't proceed until the spec is validated, and changes propagate automatically to task breakdowns and tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider agents.md for specialized personas:&lt;/strong&gt; For tools like GitHub Copilot, you can create &lt;a href="https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/" rel="noopener noreferrer"&gt;agents.md files&lt;/a&gt; that define specialized agent personas - a @docs-agent for technical writing, a @test-agent for QA, a @security-agent for code review. Each file acts as a focused spec for that persona's behavior, commands, and boundaries. This is particularly useful when you want different agents for different tasks rather than one general-purpose assistant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for Agent Experience (AX):&lt;/strong&gt; Just as we design APIs for developer experience (DX), consider designing specs for "Agent Experience." This means clean, parseable formats: OpenAPI schemas for any APIs the agent will consume, llms.txt files that summarize documentation for LLM consumption, and explicit type definitions. The Agentic AI Foundation (AAIF) is standardizing protocols like MCP (Model Context Protocol) for tool integration - specs that follow these patterns are easier for agents to consume and act on reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PRD vs SRS mindset:&lt;/strong&gt; It helps to borrow from established documentation practices. For AI agent specs, you'll often blend these into one document (as illustrated above), but covering both angles serves you well. Writing it like a PRD ensures you include user-centric context ("the why behind each feature") so the AI doesn't optimize for the wrong thing. Expanding it like an SRS ensures you nail down the specifics the AI will need to actually generate correct code (like what database or API to use). Developers have found that this extra upfront effort pays off by drastically reducing miscommunications with the agent later. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the spec a "living document":&lt;/strong&gt; Don't write it and forget it. Update the spec as you and the agent make decisions or discover new info. If the AI had to change the data model or you decided to cut a feature, reflect that in the spec so it remains the ground truth. Think of it as version-controlled documentation. In &lt;a href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/" rel="noopener noreferrer"&gt;spec-driven workflows&lt;/a&gt;, the spec drives implementation, tests, and task breakdowns, and you don't move to coding until the spec is validated. This habit keeps the project coherent, especially if you or the agent step away and come back later. Remember, the spec isn't just for the AI - it helps you as the developer maintain oversight and ensure the AI's work meets the real requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Break tasks into modular prompts and context, not one big prompt&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Divide and conquer: give the AI one focused task at a time rather than a monolithic prompt with everything at once.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Experienced AI engineers have learned that trying to stuff the entire project (all requirements, all code, all instructions) into a single prompt or agent message is a recipe for confusion. Not only do you risk hitting token limits, you also risk the model losing focus due to the "&lt;a href="https://maxpool.dev/research-papers/curse_of_instructions_report.html" rel="noopener noreferrer"&gt;curse of instructions&lt;/a&gt;" - too many directives causing it to follow none of them well. The solution is to design your spec and workflow in a modular way, tackling one piece at a time and pulling in only the context needed for that piece.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fmonolithic-prompt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fmonolithic-prompt.jpg" alt="Modular AI Specs" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The curse of too much context/instructions:&lt;/strong&gt; Research has confirmed what many devs anecdotally saw: as you pile on more instructions or data into the prompt, the model's performance in adhering to each one &lt;a href="https://openreview.net/pdf/848f1332e941771aa491f036f6350af2effe0513.pdf" rel="noopener noreferrer"&gt;drops significantly&lt;/a&gt;. One study dubbed this the "curse of instructions", showing that even GPT-4 and Claude struggle when asked to satisfy many requirements simultaneously. In practical terms, if you present 10 bullet points of detailed rules, the AI might obey the first few and start overlooking others. The better strategy is iterative focus. &lt;a href="https://maxpool.dev/research-papers/curse_of_instructions_report.html" rel="noopener noreferrer"&gt;Guidelines from industry&lt;/a&gt; suggest decomposing complex requirements into sequential, simple instructions as a best practice. Focus the AI on one sub-problem at a time, get that done, then move on. This keeps the quality high and errors manageable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Divide the spec into phases or components:&lt;/strong&gt; If your spec document is very long or covers a lot of ground, consider splitting it into parts (either physically separate files or clearly separate sections). For example, you might have a section for "Backend API Spec" and another for "Frontend UI Spec." You don't need to always feed the frontend spec to the AI when it's working on the backend, and vice versa. Many devs using multi-agent setups even create separate agents or sub-processes for each part - e.g. one agent works on database/schema, another on API logic, another on frontend - each with the relevant slice of the spec. Even if you use a single agent, you can emulate this by copying only the relevant spec section into the prompt for that task. Avoid context overload: Don't mix authentication tasks with database schema changes in one go, as the &lt;a href="https://docs.digitalocean.com/products/gradient-ai-platform/concepts/context-management/" rel="noopener noreferrer"&gt;DigitalOcean AI guide&lt;/a&gt; warns. Keep each prompt tightly scoped to the current goal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extended TOC / Summaries for large specs:&lt;/strong&gt; One clever technique is to have the agent build an extended Table of Contents with summaries for the spec. This is essentially a "spec summary" that condenses each section into a few key points or keywords, and references where details can be found. For example, if your full spec has a section on "Security Requirements" spanning 500 words, you might have the agent summarize it to: "Security: use HTTPS, protect API keys, implement input validation (see full spec §4.2)". By creating a hierarchical summary in the planning phase, you get a bird's-eye view that can stay in the prompt, while the fine details remain offloaded unless needed. This extended TOC acts as an index: the agent can consult it and say "aha, there's a security section I should look at", and you can then provide that section on demand. It's similar to how a human developer skims an outline and then flips to the relevant page of a spec document when working on a specific part.&lt;/p&gt;

&lt;p&gt;To implement this, you can prompt the agent after writing the spec: "Summarize the spec above into a very concise outline with each section's key points and a reference tag." The result might be a list of sections with one or two sentence summaries. That summary can be kept in the system or assistant message to guide the agent's focus without eating up too many tokens. This &lt;a href="https://addyo.substack.com/p/context-engineering-bringing-engineering" rel="noopener noreferrer"&gt;hierarchical summarization approach&lt;/a&gt; is known to help LLMs maintain long-term context by focusing on the high-level structure. The agent carries a "mental map" of the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Utilize sub-agents or "skills" for different spec parts:&lt;/strong&gt; Another advanced approach is using multiple specialized agents (what Anthropic calls subagents or what you might call "skills"). Each subagent is configured for a specific area of expertise and given the portion of the spec relevant to that area. For instance, you might have a Database Designer subagent that only knows about the data model section of the spec, and an API Coder subagent that knows the API endpoints spec. The main agent (or an orchestrator) can route tasks to the appropriate subagent automatically. The benefit is each agent has a smaller context window to deal with and a more focused role, which can &lt;a href="https://10xdevelopers.dev/structured/claude-code-with-subagents/" rel="noopener noreferrer"&gt;boost accuracy and allow parallel work&lt;/a&gt; on independent tasks. Anthropic's Claude Code supports this by letting you define subagents with their own system prompts and tools. "Each subagent has a specific purpose and expertise area, uses its own context window separate from the main conversation, and has a custom system prompt guiding its behavior," as their docs describe. When a task comes up that matches a subagent's domain, Claude can delegate that task to it, with the subagent returning results independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel agents for throughput:&lt;/strong&gt; Running multiple agents simultaneously is emerging as "the next big thing" for developer productivity. Rather than waiting for one agent to finish before starting another task, you can spin up parallel agents for non-overlapping work. Willison describes this as "&lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;embracing parallel coding agents&lt;/a&gt;" and notes it's "surprisingly effective, if mentally exhausting". The key is scoping tasks so agents don't step on each other - one agent codes a feature while another writes tests, or separate components get built concurrently. Orchestration frameworks like LangGraph or OpenAI Swarm can help coordinate these agents, and shared memory via vector databases (like Chroma) lets them access common context without redundant prompting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single vs. multi-agent: when to use each&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;table {&lt;br&gt;
  width: 100%;&lt;br&gt;
  border-collapse: collapse;&lt;br&gt;
  margin: 20px 0;&lt;br&gt;
  border: 1px solid #ddd;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;tr, td, th {&lt;br&gt;
    border: 1px solid #ddd;&lt;br&gt;
    padding: 8px;&lt;br&gt;
}&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Single Agent&lt;/th&gt;
&lt;th&gt;Parallel/Multi-Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strengths&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simpler setup; lower overhead; easier to debug and follow&lt;/td&gt;
&lt;td&gt;Higher throughput; handles complex interdependencies; specialists per domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Challenges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Context overload on big projects; slower iteration; single point of failure&lt;/td&gt;
&lt;td&gt;Coordination overhead; potential conflicts; needs shared memory (e.g., vector DBs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Isolated modules; small-to-medium projects; early prototyping&lt;/td&gt;
&lt;td&gt;Large codebases; one codes + one tests + one reviews; independent features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tips&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Use spec summaries; refresh context per task; start fresh sessions often&lt;/td&gt;
&lt;td&gt;Limit to 2-3 agents initially; use MCP for tool sharing; define clear boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice, using subagents or skill-specific prompts might look like: you maintain multiple spec files (or prompt templates) - e.g. SPEC_backend.md, SPEC_frontend.md - and you tell the AI, "For backend tasks, refer to SPEC_backend; for frontend tasks refer to SPEC_frontend." Or in a tool like Cursor/Claude, you actually spin up a subagent for each. This is certainly more complex to set up than a single-agent loop, but it mimics what human developers do - we mentally compartmentalize a large spec into relevant chunks (you don't keep the whole 50-page spec in your head at once; you recall the part you need for the task at hand, and have a general sense of the overall architecture). The challenge, as noted, is managing interdependencies: the subagents must still coordinate (the frontend needs to know the API contract from the backend spec, etc.). A central overview (or an "architect" agent) can help by referencing the sub-specs and ensuring consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus each prompt on one task/section:&lt;/strong&gt; Even without fancy multi-agent setups, you can manually enforce modularity. For example, after the spec is written, your next move might be: "Step 1: Implement the database schema." You feed the agent the Database section of the spec only, plus any global constraints from the spec (like tech stack). The agent works on that. Then for Step 2, "Now implement the authentication feature", you provide the Auth section of the spec and maybe the relevant parts of the schema if needed. By refreshing the context for each major task, you ensure the model isn't carrying a lot of stale or irrelevant information that could distract it. As one guide suggests: "&lt;a href="https://docs.digitalocean.com/products/gradient-ai-platform/concepts/context-management/" rel="noopener noreferrer"&gt;Start fresh: begin new sessions&lt;/a&gt; to clear context when switching between major features". You can always remind the agent of critical global rules (from the spec's Constraints section) each time, but don't shove the entire spec in if it's not all needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use in-line directives and code TODOs:&lt;/strong&gt; Another modularity trick is to use your code or spec as an active part of the conversation. For instance, scaffold your code with // TODO comments that describe what needs to be done, and have the agent fill them one by one. Each TODO essentially acts as a mini-spec for a small task. This keeps the AI laser-focused ("implement this specific function according to this spec snippet") and you can iterate in a tight loop. It's similar to giving the AI a checklist item to complete rather than the whole checklist at once.&lt;/p&gt;

&lt;p&gt;The bottom line: small, focused context beats one giant prompt. This improves quality and keeps the AI from getting "overwhelmed" by too much at once. As one set of best practices sums up, provide "One Task Focus" and "Relevant info only" to the model, and avoid dumping everything everywhere. By structuring the work into modules - and using strategies like spec summaries or sub-spec agents - you'll navigate around context size limits and the AI's short-term memory cap. Remember, a well-fed AI is like a well-fed function: give it only the &lt;a href="https://addyo.substack.com/p/context-engineering-bringing-engineering" rel="noopener noreferrer"&gt;inputs it needs for the job at hand&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Build in self-checks, constraints, and human expertise&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Make your spec not just a to-do list for the agent, but also a guide for quality control - and don't be afraid to inject your own expertise.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;A good spec for an AI agent anticipates where the AI might go wrong and sets up guardrails. It also takes advantage of what you know (domain knowledge, edge cases, "gotchas") so the AI doesn't operate in a vacuum. Think of the spec as both coach and referee for the AI: it should encourage the right approach and call out fouls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use three-tier boundaries:&lt;/strong&gt; The &lt;a href="https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/" rel="noopener noreferrer"&gt;GitHub analysis of 2,500+ agent files&lt;/a&gt; found that the most effective specs use a three-tier boundary system rather than a simple list of don'ts. This gives the agent clearer guidance on when to proceed, when to pause, and when to stop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Falways-do.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Falways-do.jpg" alt="Three tier boundaries for AI agent specs" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Always do:&lt;/strong&gt; Actions the agent should take without asking. "Always run tests before commits." "Always follow the naming conventions in the style guide." "Always log errors to the monitoring service."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Ask first:&lt;/strong&gt; Actions that require human approval. "Ask before modifying database schemas." "Ask before adding new dependencies." "Ask before changing CI/CD configuration." This tier catches high-impact changes that might be fine but warrant a human check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🚫 Never do:&lt;/strong&gt; Hard stops. "Never commit secrets or API keys." "Never edit node_modules/ or vendor/." "Never remove a failing test without explicit approval." "Never commit secrets" was the single most common helpful constraint in the study.&lt;/p&gt;

&lt;p&gt;This three-tier approach is more nuanced than a flat list of rules. It acknowledges that some actions are always safe, some need oversight, and some are categorically off-limits. The agent can proceed confidently on "Always" items, flag "Ask first" items for review, and hard-stop on "Never" items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encourage self-verification:&lt;/strong&gt; One powerful pattern is to have the agent verify its work against the spec automatically. If your tooling allows, you can integrate checks like unit tests or linting that the AI can run after generating code. But even at the spec/prompt level, you can instruct the AI to double-check: e.g. "After implementing, compare the result with the spec and confirm all requirements are met. List any spec items that are not addressed." This pushes the LLM to reflect on its output relative to the spec, catching omissions. It's a form of self-audit built into the process.&lt;/p&gt;

&lt;p&gt;For instance, you might append to a prompt: "(After writing the function, review the above requirements list and ensure each is satisfied, marking any missing ones)." The model will then (ideally) output the code followed by a short checklist indicating if it met each requirement. This reduces the chance it forgets something before you even run tests. It's not foolproof, but it helps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM-as-a-Judge for subjective checks:&lt;/strong&gt; For criteria that are hard to test automatically - code style, readability, adherence to architectural patterns - consider using "LLM-as-a-Judge." This means having a second agent (or a separate prompt) review the first agent's output against your spec's quality guidelines. Anthropic and others have found this effective for subjective evaluation. You might prompt: "Review this code for adherence to our style guide. Flag any violations." The judge agent returns feedback that either gets incorporated or triggers a revision. This adds a layer of semantic evaluation beyond syntax checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conformance testing:&lt;/strong&gt; Willison advocates building conformance suites - language-independent tests (often YAML-based) that any implementation must pass. These act as a contract: if you're building an API, the conformance suite specifies expected inputs/outputs, and the agent's code must satisfy all cases. This is more rigorous than ad-hoc unit tests because it's derived directly from the spec and can be reused across implementations. Include conformance criteria in your spec's Success section (e.g., "Must pass all cases in conformance/api-tests.yaml").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leverage testing in the spec:&lt;/strong&gt; If possible, incorporate a test plan or even actual tests in your spec and prompt flow. In traditional development, we use TDD or write test cases to clarify requirements - you can do the same with AI. For example, in the spec's Success Criteria, you might say "These sample inputs should produce these outputs…" or "the following unit tests should pass." The agent can be prompted to run through those cases in its head or actually execute them if it has that capability. Simon Willison noted that having a &lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;robust test suite&lt;/a&gt; is like giving the agents superpowers - they can validate and iterate quickly when tests fail. In an AI coding context, writing a bit of pseudocode for tests or expected outcomes in the spec can guide the agent's implementation. Additionally, you can use a dedicated "&lt;a href="https://10xdevelopers.dev/structured/claude-code-with-subagents/" rel="noopener noreferrer"&gt;test agent&lt;/a&gt;" in a subagent setup that takes the spec's criteria and continuously verifies the "code agent's" output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bring your domain knowledge:&lt;/strong&gt; Your spec should reflect insights that only an experienced developer or someone with context would know. For example, if you're building an e-commerce agent and you know that "products" and "categories" have a many-to-many relationship, state that clearly (don't assume the AI will infer it - it might not). If a certain library is notoriously tricky, mention pitfalls to avoid. Essentially, pour your mentorship into the spec. The spec can contain advice like "If using library X, watch out for memory leak issue in version Y (apply workaround Z)." This level of detail is what turns an average AI output into a truly robust solution, because you've steered the AI away from common traps.&lt;/p&gt;

&lt;p&gt;Also, if you have preferences or style guidelines (say, "use functional components over class components in React"), encode that in the spec. The AI will then emulate your style. Many engineers even include small examples in the spec, e.g., "All API responses should be JSON. E.g. {"error": "message"} for errors." By giving a quick example, you anchor the AI to the exact format you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimalism for simple tasks:&lt;/strong&gt; While we advocate thorough specs, part of expertise is knowing when to keep it simple. For relatively simple, isolated tasks, an overbearing spec can actually confuse more than help. If you're asking the agent to do something straightforward (like "center a div on the page"), you might just say, "Make sure to keep the solution concise and do not add extraneous markup or styles." No need for a full PRD there. Conversely, for complex tasks (like "implement an OAuth flow with token refresh and error handling"), that's when you break out the detailed spec. A good rule of thumb: adjust spec detail to task complexity. Don't under-spec a hard problem (the agent will flail or go off-track), but don't over-spec a trivial one (the agent might get tangled or use up context on unnecessary instructions).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintain the AI's "persona" if needed:&lt;/strong&gt; Sometimes, part of your spec is defining how the agent should behave or respond, especially if the agent interacts with users. For example, if building a customer support agent, your spec might include guidelines like "Use a friendly and professional tone," "If you don't know the answer, ask for clarification or offer to follow up, rather than guessing." These kind of rules (often included in system prompts) help keep the AI's outputs aligned with expectations. They are essentially spec items for AI behavior. Keep them consistent and remind the model of them if needed in long sessions (LLMs can "drift" in style over time if not kept on a leash).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You remain the exec in the loop:&lt;/strong&gt; The spec empowers the agent, but you remain the ultimate quality filter. If the agent produces something that technically meets the spec but doesn't feel right, trust your judgement. Either refine the spec or directly adjust the output. The great thing about AI agents is they don't get offended - if they deliver a design that's off, you can say, "Actually, that's not what I intended, let's clarify the spec and redo it." The spec is a living artifact in collaboration with the AI, not a one-time contract you can't change.&lt;/p&gt;

&lt;p&gt;Simon Willison humorously likened working with AI agents to "a very weird form of management" and even "getting good results out of a coding agent feels &lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;uncomfortably close to managing a human intern&lt;/a&gt;". You need to provide clear instructions (the spec), ensure they have the necessary context (the spec and relevant data), and give actionable feedback. The spec sets the stage, but monitoring and feedback during execution are key. If an AI was a "weird digital intern who will absolutely cheat if you give them a chance", the spec and constraints you write are how you prevent that cheating and keep them on task.&lt;/p&gt;

&lt;p&gt;Here's the payoff: a good spec doesn't just tell the AI what to build, it also helps it self-correct and stay within safe boundaries. By baking in verification steps, constraints, and your hard-earned knowledge, you drastically increase the odds that the agent's output is correct on the first try (or at least much closer to correct). This reduces iterations and those "why on Earth did it do that?" moments.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. Test, iterate, and evolve the spec (and use the right tools)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Think of spec-writing and agent-building as an iterative loop: test early, gather feedback, refine the spec, and leverage tools to automate checks.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The initial spec is not the end - it's the beginning of a cycle. The best outcomes come when you continually verify the agent's work against the spec and adjust accordingly. Also, modern AI devs use various tools to support this process (from CI pipelines to context management utilities).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fspec-refine.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fspec-refine.jpg" alt="Spec iteration loop: Test, Feedback, Refine, Tools" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous testing:&lt;/strong&gt; Don't wait until the end to see if the agent met the spec. After each major milestone or even each function, run tests or at least do quick manual checks. If something fails, update the spec or prompt before proceeding. For example, if the spec said "passwords must be hashed with bcrypt" and you see the agent's code storing plain text - stop and correct it (and remind the spec or prompt about the rule). Automated tests shine here: if you provided tests (or write them as you go), let the agent run them. In many coding agent setups, you can have an agent run npm test or similar after finishing a task. The results (failures) can then feed back into the next prompt, effectively telling the agent "your output didn't meet spec on X, Y, Z - fix it." This kind of agentic loop (code -&amp;gt; test -&amp;gt; fix -&amp;gt; repeat) is extremely powerful and is how tools like Claude Code or Copilot Labs are evolving to handle larger tasks. Always define what "done" means (via tests or criteria) and check for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iterate on the spec itself:&lt;/strong&gt; If you discover that the spec was incomplete or unclear (maybe the agent misunderstood something or you realized you missed a requirement), update the spec document. Then explicitly re-sync the agent with the new spec: "I have updated the spec as follows... Given the updated spec, adjust the plan or refactor the code accordingly." This way the spec remains the single source of truth. It's similar to how we handle changing requirements in normal dev - but in this case you're also the product manager for your AI agent. Keep version history if possible (even just via commit messages or notes), so you know what changed and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Utilize context-management and memory tools:&lt;/strong&gt; There's a growing ecosystem of tools to help manage AI agent context and knowledge. For instance, retrieval-augmented generation (RAG) is a pattern where the agent can pull in relevant chunks of data from a knowledge base (like a vector database) on the fly. If your spec is huge, you could embed sections of it and let the agent retrieve the most relevant parts when needed, instead of always providing the whole thing. There are also frameworks implementing the Model Context Protocol (MCP), which automates feeding the right context to the model based on the current task. One example is &lt;a href="https://docs.digitalocean.com/products/gradient-ai-platform/concepts/context-management/" rel="noopener noreferrer"&gt;Context7&lt;/a&gt; (context7.com), which can auto-fetch relevant context snippets from docs based on what you're working on. In practice, this might mean the agent notices you're working on "payment processing" and it pulls the "Payments" section of your spec or documentation into the prompt. Consider leveraging such tools or setting up a rudimentary version (even a simple search in your spec document).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallelize carefully:&lt;/strong&gt; Some developers run multiple agent instances in parallel on different tasks (as mentioned earlier with subagents). This can speed up development - e.g., one agent generates code while another simultaneously writes tests, or two features are built concurrently. If you go this route, ensure the tasks are truly independent or clearly separated to avoid conflicts (the spec should note any dependencies). For example, don't have two agents writing to the same file at once. One workflow is to have an agent generate code and another review it in parallel, or to have separate components built that integrate later. This is advanced usage and can be mentally taxing to manage (as Willison admitted, running multiple agents is &lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;surprisingly effective, if mentally exhausting&lt;/a&gt;!). Start with at most 2-3 agents to keep things manageable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version control and spec locks:&lt;/strong&gt; Use Git or your version control of choice to track what the agent does. &lt;a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/" rel="noopener noreferrer"&gt;Good version control habits&lt;/a&gt; matter even more with AI assistance. Commit the spec file itself to the repo. This not only preserves history, but the agent can even use git diff or blame to understand changes (LLMs are quite capable of reading diffs). Some advanced agent setups let the agent query the VCS history to see when something was introduced - surprisingly, models can be "fiercely competent at Git". By keeping your spec in the repo, you allow both you and the AI to track evolution. There are tools (like GitHub Spec Kit mentioned earlier) that integrate spec-driven development into the git workflow - for instance, gating merges on updated specs or generating checklists from spec items. While you don't need those tools to succeed, the takeaway is to treat the spec like code - maintain it diligently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost and speed considerations:&lt;/strong&gt; Working with large models and long contexts can be slow and expensive. A practical tip is to use model selection and batching smartly. Perhaps use a cheaper/faster model for initial drafts or repetitions, and reserve the most capable (and expensive) model for final outputs or complex reasoning. Some developers use GPT-4 or Claude for planning and critical steps, but offload simpler expansions or refactors to a local model or a smaller API model. If using multiple agents, maybe not all need to be top-tier; a test-running agent or a linter agent could be a smaller model. Also consider throttling context size: don't feed 20k tokens if 5k will do. As we discussed, &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;more tokens can mean diminishing returns&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor and log everything:&lt;/strong&gt; In complex agent workflows, logging the agent's actions and outputs is essential. Check the logs to see if the agent is deviating or encountering errors. Many frameworks provide trace logs or allow printing the agent's chain-of-thought (especially if you prompt it to think step-by-step). Reviewing these logs can highlight where the spec or instructions might have been misinterpreted. It's not unlike debugging a program - except the "program" is the conversation/prompt chain. If something weird happens, go back to the spec/instructions to see if there was ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn and improve:&lt;/strong&gt; Finally, treat each project as a learning opportunity to refine your spec-writing skill. Maybe you'll discover that a certain phrasing consistently confuses the AI, or that organizing spec sections in a certain way yields better adherence. Incorporate those lessons into the next spec. The field of AI agents is rapidly evolving, so new best practices (and tools) emerge constantly. Stay updated via blogs (like the ones by Simon Willison, Andrej Karpathy, etc.), and don't hesitate to experiment.&lt;/p&gt;

&lt;p&gt;A spec for an AI agent isn't "write once, done." It's part of a continuous cycle of instructing, verifying, and refining. The payoff for this diligence is substantial: by catching issues early and keeping the agent aligned, you avoid costly rewrites or failures later. As one AI engineer quipped, using these practices can feel like having "an army of interns" working for you, but you have to manage them well. A good spec, continuously maintained, is your management tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Avoid common pitfalls&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before wrapping up, it's worth calling out anti-patterns that can derail even well-intentioned spec-driven workflows. The &lt;a href="https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/" rel="noopener noreferrer"&gt;GitHub study of 2,500+ agent files&lt;/a&gt; revealed a stark divide: "Most agent files fail because they're too vague." Here are the mistakes to avoid:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vague prompts:&lt;/strong&gt; "Build me something cool" or "Make it work better" gives the agent nothing to anchor on. As Baptiste Studer puts it: "Vague prompts mean wrong results." Be specific about inputs, outputs, and constraints. "You are a helpful coding assistant" doesn't work. "You are a test engineer who writes tests for React components, follows these examples, and never modifies source code" does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overlong contexts without summarization:&lt;/strong&gt; Dumping 50 pages of documentation into a prompt and hoping the model figures it out rarely works. Use hierarchical summaries (as discussed in Principle 3) or RAG to surface only what's relevant. Context length is not a substitute for context quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping human review:&lt;/strong&gt; Willison has a personal rule: "I won't commit code I couldn't explain to someone else." Just because the agent produced something that passes tests doesn't mean it's correct, secure, or maintainable. Always review critical code paths. The "house of cards" metaphor applies: AI-generated code can look solid but collapse under edge cases you didn't test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conflating vibe coding with production engineering:&lt;/strong&gt; Rapid prototyping with AI ("vibe coding") is great for exploration and throwaway projects. But shipping that code to production without rigorous specs, tests, and review is asking for trouble. I distinguish "vibe coding" from "AI-assisted engineering" - the latter requires the discipline this guide describes. Know which mode you're in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring the "lethal trifecta":&lt;/strong&gt; Willison warns of three properties that make AI agents dangerous: speed (they work faster than you can review), non-determinism (same input, different outputs), and cost (encouraging corner-cutting on verification). Your spec and review process must account for all three. Don't let speed outpace your ability to verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing the six core areas:&lt;/strong&gt; If your spec doesn't cover commands, testing, project structure, code style, git workflow, and boundaries, you're likely missing something the agent needs. Use the six-area checklist from Section 2 as a sanity check before handing off to the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Writing an effective spec for AI coding agents requires solid software engineering principles combined with adaptation to LLM quirks. Start with clarity of purpose and let the AI help expand the plan. Structure the spec like a serious design document - covering the six core areas and integrating it into your toolchain so it becomes an executable artifact, not just prose. Keep the agent's focus tight by feeding it one piece of the puzzle at a time (and consider clever tactics like summary TOCs, subagents, or parallel orchestration to handle big specs). Anticipate pitfalls by including three-tier boundaries (Always/Ask first/Never), self-checks, and conformance tests - essentially, teach the AI how to not fail. And treat the whole process as iterative: use tests and feedback to refine both the spec and the code continuously.&lt;/p&gt;

&lt;p&gt;Follow these guidelines and your AI agent will be far less likely to "break down" under large contexts or wander off into nonsense. &lt;/p&gt;

&lt;p&gt;Happy spec-writing!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm excited to share I've released a new &lt;a href="https://beyond.addy.ie/" rel="noopener noreferrer"&gt;AI-assisted engineering book&lt;/a&gt; with O'Reilly. There are a number of free tips on the book site in case interested.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>coding</category>
    </item>
    <item>
      <title>Incremental Code Migrations</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Sun, 08 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/addyosmani/incremental-code-migrations-12o4</link>
      <guid>https://forem.com/addyosmani/incremental-code-migrations-12o4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Incremental migrations are excellent for updating large legacy projects - migrate a small piece at a time with lower risk then gradually do the rest.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Incremental migration strategies for large legacy software systems include breaking down the system into smaller components that can be selectively and individually replaced. This phased migration reduces the expense and risk of a migration compared to doing all of an update or rewrite at once (e.g a phased migration to TypeScript, ES Modules or a newer version of a framework). In some cases, some of the legacy components might be encapsulated in wrappers so they can be used in their original form until new components with a decent degree of reliability can take over their function. &lt;/p&gt;

&lt;p&gt;A &lt;a href="http://www.dcs.ed.ac.uk/home/rgd/sw/migrateIncrementally.html" rel="noopener noreferrer"&gt;phased migration&lt;/a&gt; from the legacy architecture to the target one can have a lot of benefits. When done well, there can be a fully functioning hybrid legacy/target version of the system running in production while getting key insights into what works and what has rough edges. You may wish to prioritize the order in which phases of your migration take place keeping in mind what the most pressing objectives need to be tackled first. Alternatively, first tackle mission-critical stages to lift the profile of the project and get buy-in to see through the full migration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqx2yn7iqe03rp81e6181.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqx2yn7iqe03rp81e6181.png" alt="incremental migration, parallel adoption and big bang adoption." width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the system is still operational after each phase of the migration, it is possible to appreciate the benefits of the project faster than if the entire legacy were migrated and implemented at once. Often, bugs in a small component are easier to identify and fix than in a large system. This decreases the risk and inconvenience of the project to users. &lt;/p&gt;

&lt;p&gt;There are a number of alternatives to incremental or &lt;a href="https://en.wikipedia.org/wiki/Phased_adoption" rel="noopener noreferrer"&gt;phased migration&lt;/a&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Big_bang_adoption" rel="noopener noreferrer"&gt;Big bang adoption&lt;/a&gt; where a full migration is rolled out on an agreed on date, replacing the old system with a new system. This is effectively an instant changeover.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Parallel_adoption" rel="noopener noreferrer"&gt;Parallel adoption&lt;/a&gt; where both old and new systems run in parallel (simultaneously) for a time, until the implementation of the new system is considered complete.&lt;/li&gt;
&lt;li&gt;Pilot conversion where a very partial migration is rolled out to a group of trusted testers for evaluation before the implementation rolls out to more users (e.g trying out a migration for a specific component or page).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tend to favor incremental migrations for many reasons, including that lessons gained from phased migration phases can be used to direct the remainder of the project. That way as the implementation progresses, there are ideally fewer issues.&lt;/p&gt;

</description>
      <category>codemigration</category>
      <category>phasedmigration</category>
      <category>rewrites</category>
      <category>rewrite</category>
    </item>
    <item>
      <title>Monitoring Performance with the PageSpeed Insights API</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Mon, 06 Jan 2020 17:57:42 +0000</pubDate>
      <link>https://forem.com/addyosmani/monitoring-performance-with-the-pagespeed-insights-api-33k7</link>
      <guid>https://forem.com/addyosmani/monitoring-performance-with-the-pagespeed-insights-api-33k7</guid>
      <description>&lt;p&gt;The &lt;a href="https://developers.google.com/speed/docs/insights/v5/get-started" rel="noopener noreferrer"&gt;PageSpeed Insights API&lt;/a&gt; provides free access to performance monitoring for web pages and returns data with suggestions for how to improve. The V5 API includes lab data from &lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; and real-world data from the &lt;a href="https://developers.google.com/web/tools/chrome-user-experience-report/" rel="noopener noreferrer"&gt;Chrome User Experience Report&lt;/a&gt; (CrUX).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wordpress.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Teams use the API to build dashboards, custom reports and custom integrations with other user-experience measurement tools. The &lt;a href="https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://web.dev" rel="noopener noreferrer"&gt;responses&lt;/a&gt; from the API could be used to monitor and graph any of the data from the &lt;a href="https://developers.google.com/speed/pagespeed/insights/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; (PSI) tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fu4qdum9zuzpegcgfd5ub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fu4qdum9zuzpegcgfd5ub.png" alt="pagespeed insights v5 report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;img alt="psi-score" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F57akesqxqn34287w0t6t.png"&gt;&lt;/td&gt;
            &lt;td&gt;The PSI score (&lt;strong&gt;json.lighthouseResult.categories.performance.score&lt;/strong&gt;) is determined by running Lighthouse to analyze lab data about the page. A score at or above 90 is considered fast and below 50 is considered to be slow. See the &lt;a href="https://developers.google.com/speed/docs/insights/v5/about" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; for the latest on scoring and &lt;a href="http://bit.ly/perf-variance" rel="noopener noreferrer"&gt;bit.ly/perf-variance&lt;/a&gt; to learn about score variance.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;img alt="psi fcp" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F79cwcgeaqgpxe31rm24p.png"&gt;&lt;/td&gt;
            &lt;td&gt;If available, PSI will report field metric values (&lt;strong&gt;json.loadingExperience.metrics&lt;/strong&gt;) for First Contentful Paint and First Input Delay data from the Chrome User Experience Report for the origin or page URL.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;img alt="psi lab" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffcistgkvi0g8eejdwvq0.png"&gt;&lt;/td&gt;
            &lt;td&gt;PSI uses Lighthouse to analyze a URL, generating a performance score that factors in a number of different metrics in a lab setting, like Time to Interactive (&lt;strong&gt;json.lighthouseResult.audits['interactive']&lt;/strong&gt;). &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;img alt="psi opportunities" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fndzeuvsbolityvaqk5ut.png"&gt;&lt;/td&gt;
            &lt;td&gt;The Lighthouse report opportunities (e.g &lt;strong&gt;json.lighthouseResult.audits['uses-rel-preload']&lt;/strong&gt;, &lt;strong&gt;json.lighthouseResult.audits['offscreen-images']&lt;/strong&gt; etc.) provide suggestions how to improve the page’s performance metrics.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&lt;img alt="psi opportunities" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99bh4bdevelrr8hwc2ft.png"&gt;&lt;/td&gt;
            &lt;td&gt;Thumbnail screenshots from the load of your site are available as base64 images via &lt;strong&gt;json.lighthouseResult.audits['screenshot-thumbnails']&lt;/strong&gt;. The last screenshot from pageload is available via &lt;strong&gt;json.lighthouseResult.audits['final-screenshot']&lt;/strong&gt;.&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's possible to build highly customized reports using PSI data. e.g VRBO, a vacation rentals site, graph real-world data from the PSI API to &lt;a href="https://youtu.be/iaWLXf1FgI0?t=650" rel="noopener noreferrer"&gt;track long term performance trends&lt;/a&gt; to ensure their speed remains competitive within the travel industry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs4zq6yupk51po85vyyzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs4zq6yupk51po85vyyzh.png" alt="pagespeed insights rum"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/measure" rel="noopener noreferrer"&gt;web.dev/measure&lt;/a&gt; uses the exact same backend as PageSpeed Insights and uses this data for historical measurement over time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Foys8cnrq2mp7mt33i0ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Foys8cnrq2mp7mt33i0ux.png" alt="web.dev/measure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's helpful to become familiar with the &lt;a href="https://developers.google.com/speed/docs/insights/v5/reference/pagespeedapi/runpagespeed#response" rel="noopener noreferrer"&gt;structure of the PSI API response&lt;/a&gt;. There's extensive metrics info available for lab and field/real-world:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wordpress.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Real-world metrics&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cruxMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First Contentful Paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadingExperience&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FIRST_CONTENTFUL_PAINT_MS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First Input Delay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadingExperience&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FIRST_INPUT_DELAY_MS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="c1"&gt;// Lab metrics&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lighthouse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lighthouseResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lighthouseMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;First Contentful Paint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lighthouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first-contentful-paint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Speed Index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lighthouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;speed-index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Time To Interactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lighthouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Responses from the PSI API are focused on performance data. That said, you can supply the &lt;code&gt;?category&lt;/code&gt; argument to specify any additional Lighthouse audit categories you would like data for:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://web.dev&amp;amp;category=pwa&amp;amp;category=performance&amp;amp;category=accessibility&amp;amp;category=best-practices&amp;amp;category=seo"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can also supply a &lt;code&gt;locale&lt;/code&gt; or &lt;code&gt;strategy&lt;/code&gt; argument (&lt;code&gt;desktop&lt;/code&gt; or &lt;code&gt;mobile&lt;/code&gt; - which simulates a page load on a median-class device (Moto G4) on a mobile network).&lt;/p&gt;

&lt;p&gt;Lighthouse is starting to support &lt;a href="https://developers.google.com/web/updates/2019/01/lighthouse-platform-packs" rel="noopener noreferrer"&gt;Stack Packs&lt;/a&gt; - stack-specific recommendations, providing more detailed guidance on how to implement optimizations (e.g WordPress). PSI's API responses also support this so if you're testing a WordPress site, these strings are included (&lt;a href="https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://wordpress.org" rel="noopener noreferrer"&gt;demo URL for wordpress.org&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frv6y90wcbt16ud92gx0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frv6y90wcbt16ud92gx0m.png" alt="pagespeed insights wordpress"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The PageSpeed Insights Tool also supports fetching PSI API data and rendering it with our official &lt;a href="https://googlechrome.github.io/lighthouse/viewer" rel="noopener noreferrer"&gt;Lighthouse Viewer&lt;/a&gt;. Pass &lt;code&gt;?psiurl&lt;/code&gt; as a &lt;a href="https://googlechrome.github.io/lighthouse/viewer/?psiurl=https://web.dev&amp;amp;category=accessibility&amp;amp;category=performance&amp;amp;category=pwa&amp;amp;category=best-practices&amp;amp;category=seo" rel="noopener noreferrer"&gt;parameter&lt;/a&gt; to see this in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fl123uat6nwv58zpag8fe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fl123uat6nwv58zpag8fe.png" alt="pagespeed insights lighthouse viewer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the tools we built on top of PSI is the &lt;a href="https://github.com/GoogleChromeLabs/psi" rel="noopener noreferrer"&gt;psi&lt;/a&gt; Node module, offering convenient performance reporting in your build process.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;psi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;psi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Get the PageSpeed Insights report&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;psi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://web.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Speed score:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lighthouseResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Output a formatted report to the terminal&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;psi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://theverge.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output of &lt;code&gt;psi&lt;/code&gt; looks a little like this when used as a CLI with PageSpeed Insights V5:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjzygdf3q2mzov4d5phr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjzygdf3q2mzov4d5phr3.png" alt="pagespeed insights node module build process output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try out the PSI API, check out this &lt;a href="https://glitch.com/~psi-demo" rel="noopener noreferrer"&gt;Glitch demo&lt;/a&gt; using both Lighthouse and CrUX data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnlizibqpkipterbkf8gx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnlizibqpkipterbkf8gx.png" alt="pagespeed insights live demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use &lt;a href="https://sheets.google.com" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; and a cron job to automate monitoring multiple URLs (e.g competitors) by regularly pinging the PSI API. A handy &lt;a href="https://dev.to/chromiumdev/a-step-by-step-guide-to-monitoring-the-competition-with-the-chrome-ux-report-4k1o"&gt;guide&lt;/a&gt; and sheet you can copy are available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnnuhaxf3hjaxabktyz63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnnuhaxf3hjaxabktyz63.png" alt="pagespeed insights google sheets"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  What about third-party resources?
&lt;/h2&gt;

&lt;p&gt;We know third-party scripts are costly. In the top 4 million sites, &lt;a href="https://github.com/patrickhulce/third-party-web" rel="noopener noreferrer"&gt;~2700 origins&lt;/a&gt; are responsible for ~57% of all script execution time. Lighthouse exposes an impact summary for third-party code, available via the API from &lt;strong&gt;json.lighthouseResult.audits['third-party-summary']&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F931ppc3mh0pvgli0wm6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F931ppc3mh0pvgli0wm6q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PageSpeed Insights API Limits
&lt;/h2&gt;

&lt;p&gt;For infrequent usage, the PSI API functions without an API key. &lt;/p&gt;

&lt;p&gt;If you intend to make multiple queries per second however, it is recommended to sign up for a PSI &lt;a href="https://developers.google.com/speed/docs/insights/v5/get-started" rel="noopener noreferrer"&gt;API key&lt;/a&gt;. API keys have a daily limit of 25,000 queries a day or 400 queries per 100 seconds. This should be enough for most dashboarding needs. It is also possible to request a higher quota if needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhvzzw6hmt5g1qynsqk6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhvzzw6hmt5g1qynsqk6w.png" alt="pagespeed insights api limits"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next for the PSI API?
&lt;/h2&gt;

&lt;p&gt;We hope to bring metrics like &lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;Largest Contentful Paint&lt;/a&gt; and &lt;a href="https://web.dev/cls/" rel="noopener noreferrer"&gt;Cumulative Layout Shift&lt;/a&gt; to Lighthouse, CrUX and PageSpeed Insights in 2020. We may also explore better hosted API options for obtaining field-data from CrUX. In the interim, you may be interested in &lt;a href="https://crux.run/" rel="noopener noreferrer"&gt;Crux.run&lt;/a&gt;, a fast mirror of CrUX reporting data which also has an API. &lt;/p&gt;

&lt;h2&gt;
  
  
  Additional reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://calibreapp.com/blog/how-pagespeed-works" rel="noopener noreferrer"&gt;How PageSpeed Works - Calibre Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.debugbear.com/blog/why-is-my-lighthouse-score-different-from-pagespeed-insights" rel="noopener noreferrer"&gt;Why is my Lighthouse score different from PSI? - DebugBear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@treo/pagespeed-insights-monitoring-4109c9c01a10" rel="noopener noreferrer"&gt;PageSpeed Insights Monitoring - Treo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ithoughthecamewithyou.com//post/automate-google-pagespeed-insights-with-apps-script" rel="noopener noreferrer"&gt;Automate Google PageSpeed Insights with Apps Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/chromiumdev/a-step-by-step-guide-to-monitoring-the-competition-with-the-chrome-ux-report-4k1o"&gt;Monitoring the competition with the Chrome UX Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/expedia-group-tech/whats-in-the-google-pagespeed-score-a5fc93f91e91" rel="noopener noreferrer"&gt;What's in the PageSpeed score?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/treosh/lighthouse-plugin-field-performance" rel="noopener noreferrer"&gt;Treo Lighthouse field performance plugin&lt;/a&gt; (uses the API)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;With thanks to Shane Exterkamp for his review&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pagespeedinsights</category>
      <category>webperf</category>
      <category>monitoring</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Loading web pages fast on a $20 feature phone</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Tue, 17 Dec 2019 18:59:49 +0000</pubDate>
      <link>https://forem.com/addyosmani/loading-web-pages-fast-on-a-20-feature-phone-8h6</link>
      <guid>https://forem.com/addyosmani/loading-web-pages-fast-on-a-20-feature-phone-8h6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Tip: Building a fast, core foundation for your site gives everyone a good experience; whether they're on a low-cost feature phone or the latest high-end smart phone.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Feature phones are affordable (under $20-25), low-end devices enabling 100s of millions of users in developing countries to leverage the web&lt;/strong&gt;. Think of them as a light version of a smart phone.  &lt;/p&gt;

&lt;p&gt;Being low-cost, feature phones tend to have slower CPUs (think 6x slower than a high-end smart phone), low RAM (e.g 256MB-512MB, typically under 4GB), low storage (4G) and often don't have a touchscreen. Instead they use a keypad or D-Pad for navigation. Something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hO0kjKFn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7m6im6bgpz5j2i4afqtv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hO0kjKFn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7m6im6bgpz5j2i4afqtv.jpg" alt="JioPhone model being held"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These devices can't handle rich JavaScript &amp;amp; media experiences as well as high-end smart phones so special care must be taken with payloads you send down. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ6Gpd4c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/fgot72vc5qk21zr886ve.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ6Gpd4c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/fgot72vc5qk21zr886ve.png" alt="CPU performance of popular smart phones compared to a feature phone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above, we can see &lt;a href="https://www.geekbench.com/"&gt;Geekbench&lt;/a&gt; CPU performance benchmarks for the highest selling smart phones globally in 2019. We can also see (highlighted) the performance of a popular feature phone - the Nokia 3110. JavaScript stresses single-core performance (remember it's inherently more single-threaded than the rest of the Web Platform) and is &lt;a href="https://v8.dev/blog/cost-of-javascript-2019"&gt;CPU bound&lt;/a&gt;. This means keeping device characteristics in mind when thinking about developing countries is important.&lt;/p&gt;

&lt;p&gt;This post attempts to address these pain points so we can build sites that are usable by everyone, regardless of how fast their device is.&lt;/p&gt;

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;You may or may not remember that feature phones were popular until the mid 2000s, before the arrival of smart phones. Small in size with a key-pad instead of a touchscreen, they had pretty basic features e.g calling, texting and simple text-focused web browsing. After the arrival of smart phones, these phones became less prevalent in developed countries. &lt;/p&gt;

&lt;p&gt;In developing countries, not everyone can afford a smart phone and an unlimited data-plan on a 4G network. This market has been captured by smart feature phones which combine the hardware and price of a basic phone with some of the functionality found in smart phones. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7PqBnMWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9evcjuavzzplbhavqgf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7PqBnMWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9evcjuavzzplbhavqgf2.png" alt="Price ranges for feature phones"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The smart feature phone market has grown considerably since 2017 and it is &lt;a href="https://www.counterpointresearch.com/more-than-a-billion-feature-phones-to-be-sold-over-next-three-years/"&gt;expected&lt;/a&gt; that 400 million feature phones will be sold globally in 2019. &lt;/p&gt;

&lt;p&gt;The growth of feature phones has been powered by Nokia’s revival of its old favourites like the 3110 and 8110 (which Paul Kinlan has a handy &lt;a href="https://paul.kinlan.me/debugging-web-pages-on-the-nokia-8110-with-kaios/"&gt;debugging guide&lt;/a&gt; for). In India, &lt;a href="https://www.jio.com/"&gt;Reliance Jio&lt;/a&gt; feature phones provide a cheap, but modern option for accessing the web on the go. Jio have propelled the growth of &lt;a href="https://www.kaiostech.com/"&gt;KaiOS&lt;/a&gt; - a Linux-based operating system for feature phones.&lt;/p&gt;

&lt;p&gt;This growth in the feature phone market created a need for sites that can run efficiently but there are a few constraints we should probably be aware of.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rmo4ZMqk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f959syqj3hpmgva8nitb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rmo4ZMqk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f959syqj3hpmgva8nitb.jpg" alt="Google Images Lite, Facebook mBasic and Proxx"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above are Google Images Lite and Facebook mBasic, which both load quickly on feature phones, with minimal reliance on client-side script. &lt;a href="http://proxx.app/"&gt;Proxx&lt;/a&gt; being a game does rely on script, but &lt;a href="https://web.dev/load-faster-like-proxx/"&gt;relies&lt;/a&gt; on aggressive code-splitting to load fast.&lt;/p&gt;

&lt;h1&gt;
  
  
  Feature phone constraints
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Users in developing countries are restricted by 3 factors:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Few low cost, high quality devices&lt;/li&gt;
&lt;li&gt;Unavailability of high quality networks&lt;/li&gt;
&lt;li&gt;Affordable mobile data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep these constraints in mind when adopting a feature phone mindset:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Hardware&lt;/strong&gt;: Feature phones usually run on a slow (1.1 GHz) single or dual core CPU and less than 512 MB RAM. Compare this to an iPhone XS with a Hexa-core CPU and 4 GB RAM for some perspective on what this constraint implies.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data:&lt;/strong&gt; Data plans are getting cheaper, but are still heavily capped in regions where feature phones will be popular. Avoid large network payloads as much as possible to ensure your pages load fast and aren't costly users money.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Limited screen size:&lt;/strong&gt; The screen size of a feature phone is typically much smaller than that of a smart phone. It’s in the range of 2.4” and can accommodate only a limited number of interactions. Consider focusing on loading resources needed for in-viewport content as quick as possible.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Touch:&lt;/strong&gt; In the absence of touch, each function, action button or link on the screen needs to be easily accessible from the keypad. You don't want to create too many shortcuts. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Keypads:&lt;/strong&gt; Keypads on feature phones are nothing like the QWERTY keyboards we are used to. They have around 15 buttons where the same button needs to be pressed repeatedly for some characters. Hence, UX should minimize the need for typing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even in Japan, limited data plans can impact user experience on the web:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--26teMJjY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EJWfWGnVUAA6jnz.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--4pSdsZg2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/879967395657515008/DGugBIf2_normal.jpg" alt="Yoav Weiss profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Yoav Weiss
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @yoavweiss
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8bbACBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-99c56e7c338b4d5c17d78f658882ddf18b0bbde5b3f42f84e7964689e7e8fb15.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      In Japan, the web is slower towards the end of the month due to data caps, resulting in reduced engagement 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      17:51 PM - 14 Nov 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1195036487538003968" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1195036487538003968" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      1566
      &lt;a href="https://twitter.com/intent/like?tweet_id=1195036487538003968" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      3627
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Development Guidelines
&lt;/h1&gt;

&lt;p&gt;The following tips can help deliver a fast experience for websites on feature phones. In general, &lt;strong&gt;don't make the user wait for anything they didn't ask for.&lt;/strong&gt; Where possible, keep JavaScript &lt;a href="https://v8.dev/blog/cost-of-javascript-2019"&gt;download and execution&lt;/a&gt; times low. &lt;/p&gt;

&lt;h2&gt;
  
  
  Set performance budgets for your initial payloads
&lt;/h2&gt;

&lt;p&gt;Every byte of your page goes through many bottlenecks along the way. These include slow/flaky networks, slow CPUs and the only guaranteed way to improve performance is set budgets and do less. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/performance-budgets-101"&gt;Performance budgets&lt;/a&gt; are an agreed upon set of limits your team follow to ensure good performance. They are constraints for metrics that you commit not to exceed. Defining a budget for quantifiable metrics before development starts helps ensure teams remain within these standards as new features are developed.&lt;/p&gt;

&lt;p&gt;Examples of resource metrics that can be budgeted could be JavaScript bundle size, image bytes or number of HTTP requests. User timing budgets could be set for user-experience metrics like &lt;a href="https://web.dev/first-contentful-paint"&gt;First Contentful Paint&lt;/a&gt;, &lt;a href="https://web.dev/lcp/"&gt;Largest Contentful Paint&lt;/a&gt; or &lt;a href="https://developers.google.com/web/updates/2018/05/first-input-delay"&gt;First Input Delay&lt;/a&gt;. Thresholds can be defined for each of these metrics based on the target audience. &lt;/p&gt;

&lt;p&gt;Budgets can be set for initial application logic, vendor/commons bundles and so on. Budget can be enforced in a &lt;a href="https://web.dev/incorporate-performance-budgets-into-your-build-tools/"&gt;build process&lt;/a&gt;, via &lt;a href="https://web.dev/use-lighthouse-for-performance-budgets/"&gt;Lighthouse&lt;/a&gt; (LightWallet) and in &lt;a href="https://web.dev/using-bundlesize-with-travis-ci/"&gt;continuous&lt;/a&gt; &lt;a href="https://web.dev/using-lighthouse-bot-to-set-a-performance-budget/"&gt;integration&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  PRPL-30 - a JavaScript budget for feature phones
&lt;/h3&gt;

&lt;p&gt;The Chrome team has recommended the &lt;strong&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/prpl-pattern/"&gt;PRPL pattern&lt;/a&gt;&lt;/strong&gt; for granularly serving code to &lt;em&gt;quickly get apps interactive&lt;/em&gt; on low-end devices on a slow network. PRPL encourages preloading the smallest amount of script to get a page useful then leverages lazy-loading and (optionally) Service Workers to precache these bundles for future navigations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PRPL-50&lt;/strong&gt; took this idea further by setting a 50KB budget for your initial resources. As feature phones are even more CPU-constrained, we need to set an even tighter limit on our JavaScript. I might suggest a &lt;strong&gt;PRPL-30&lt;/strong&gt; (30KB gzipped + minified initial bundle) if targeting a feature phone. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLY01fL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vsobe85mjrdokvaixcm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLY01fL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vsobe85mjrdokvaixcm7.png" alt="JS budget breaks down into app logic, framework, utilities and other third-party dependencies. It's not a lot."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under these settings, the first byte from a good edge-caching server after SSL negotiation will usually be around 2s. This leaves us with 3 seconds to get the initial route's payload downloaded, rendered, and ready for user input on the screen. For JavaScript-centric experiences, this means your total initial bundle size for your page or route should be &amp;lt; 30KB minified and gzipped.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5AdWAjF9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dh016epxpehjk0t8quh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5AdWAjF9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dh016epxpehjk0t8quh5.png" alt="PRPL-30 gives you a chance to get to an interactive experience in under 5s on a feature phone with a very slow CPU"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait. A 30KB initial bundle? That's ridiculous. I can't even fit React and my app code in there!&lt;/strong&gt;. When building for really constrained devices, you're going to have to make difficult trade-offs in the name of user-experience. For example, you &lt;em&gt;can&lt;/em&gt; use React for feature phones if you either (1) limit use of React to the server - a totally fair option or (2) keep your chunks for application logic very, very small and aggressively lazy-load. A (3) would be opting for a solution like Preact, but we'll discuss these trade-offs more later.&lt;/p&gt;

&lt;p&gt;An example of an application that works under this 30KB budget is &lt;a href="https://web.dev/proxx-announce/"&gt;Proxx&lt;/a&gt; which has a 25 KB initial size and a Time to Interaction (TTI) of less than 5 seconds. Our &lt;a href="https://perf-budget-calculator.firebaseapp.com/"&gt;perf budgets calculator&lt;/a&gt; may be helpful as you’re planning out your own target metrics.&lt;/p&gt;

&lt;p&gt;A recommended size for lazy-loaded routes is also less than 35 KB. Route "chunks" between 30 KB and 35 KB would still be large enough to be considered for parallel processing via &lt;a href="https://blog.chromium.org/2015/03/new-javascript-techniques-for-rapid.html"&gt;V8's script streaming&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Be Frugal with JavaScript
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;tl;dr If you can, opt for &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#static-rendering"&gt;static rendering&lt;/a&gt; or &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#server-rendering"&gt;server rendering&lt;/a&gt; with as minimal reliance on script as possible. If client-side or hybrid rendering is a must, only send script a route needs, in as few round trips as possible. Consider techniques like &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#progressive-rehydration"&gt;progressive rehydration&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zQulltlz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/p5is6kbv4dr56y6hp0mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zQulltlz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/p5is6kbv4dr56y6hp0mn.png" alt="static rendering" width="300px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  JS is the #1 bottleneck on feature phones
&lt;/h3&gt;

&lt;p&gt;When building interactive experiences for feature phones, be aware that JavaScript is likely your biggest bottleneck. This is important as how you choose to &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web"&gt;render your pages&lt;/a&gt; can delay how soon users can actually use them, even with a D-Pad. If you choose to &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#server-rendering"&gt;server-render&lt;/a&gt; or &lt;a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#static-rendering"&gt;static render&lt;/a&gt; your pages, ensure interactive payloads are as small as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WDsbohbE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/6wb9uvk8frdanwc0e3r4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WDsbohbE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/6wb9uvk8frdanwc0e3r4.png" alt="Enter URL -&amp;gt; SSR payload. -&amp;gt; interactive payload -&amp;gt; active"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JavaScript has two key costs: &lt;a href="https://v8.dev/blog/cost-of-javascript-2019"&gt;download and execution time&lt;/a&gt;. A slow network (e.g effective connection of 3G) can delay how quickly JavaScript gets over the wire while a slow CPU means it can take longer for scripts to execute. Below we can visualize the differences in CPU processing speeds for a popular JavaScript-heavy site - notice it can take 6x times longer for a low-end device compared to a high-end one:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU performance comparison between different smart phones&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mVj1EQpa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/450mh64w6jjbtydz9a2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mVj1EQpa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/450mh64w6jjbtydz9a2a.png" alt="JS processing times for Reddit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a page relies heavily on large JS bundles for rendering or interactivity, this can translate to users waiting anywhere between 30-60s before they can even use your UI on a feature phone. Thus to minimize the download and processing time required by JavaScript, developers need to use it frugally and load only the JavaScript for routes and components that may be required by the users, as and when they would need it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Keep interactive payloads lean
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; As much as possible, lazy-load routes, components and resources that are offscreen or not critical for above-the-fold content. &lt;/li&gt;
&lt;li&gt; Use ​&lt;a href="https://web.dev/reduce-javascript-payloads-with-code-splitting/"&gt;code-splitting&lt;/a&gt;​ to break up JavaScript so you only serve what users need on the initial route. This would result in faster page load time as amount of script to be downloaded and executed is reduced.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://web.dev/remove-unused-code/"&gt;Remove unused code&lt;/a&gt;​ from JavaScript bundles to keep them as lean as possible. This would require you to analyse your bundle and detect the libraries which are not being used at all or not required and may be replaced by custom ones. Libraries which are not required during the initial load may also be lazy loaded.&lt;/li&gt;
&lt;li&gt; Consider ​&lt;a href="https://web.dev/serve-modern-code-to-modern-browsers/"&gt;differential loading&lt;/a&gt;​ so that modern JS will be served to modern browsers, avoiding over-transpilation and excessive polyfilling. Reducing how much legacy code is shipped to modern browsers can help improve page load performance.&lt;/li&gt;
&lt;li&gt; If JS is critical for rendering &amp;amp; getting the initial UX interactive,​ &lt;a href="https://web.dev/preload-critical-assets/"&gt;preload&lt;/a&gt;​ those scripts. Preloading the script as shown below tells the browser that it is important and should be fetched as soon as possible.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"script"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"critical.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;‬
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Choose your stack wisely
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wFOWSw2H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/r8jj58e7t5rrnk2p8i0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wFOWSw2H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/r8jj58e7t5rrnk2p8i0o.png" alt="framework vs utilities"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While third party libraries help to speed up development and easily achieve complex tasks, they can also be heavy and should be used with care when developing for feature phones. The following guidance can help to optimally incorporate external dependencies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; As feature phones are very resource constrained, avoid or limit using a JavaScript framework if possible - it's all about giving yourself as much  headroom as possible for your application logic. In a situation where JavaScript use should be limited in order to keep the page small and performant, a JavaScript framework would add a lot of overhead. If building with React, consider limiting use to server-rendering or swap it for &lt;a href="https://preactjs.com/"&gt;Preact&lt;/a&gt; and ​&lt;a href="https://preactjs.com/guide/switching-to-preact"&gt;Preact compat&lt;/a&gt;​ at build time to shave around 30KB off your bundle. &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt;, &lt;a href="https://lit-html.polymer-project.org/"&gt;lit-html&lt;/a&gt; and vanilla JavaScript are all good options for light bundles. &lt;/li&gt;
&lt;li&gt; Keep third party dependencies as small as possible to ensure that the size of initial page bundles is within budget- see tools like &lt;a href="https://bundlephobia.com/"&gt;bundlephobia.com&lt;/a&gt; which highlight library costs well. Sanity check your bundles to ensure you’re using lean libraries (E.g. &lt;a href="https://date-fns.org/"&gt;date-fns&lt;/a&gt; or &lt;a href="https://moment.github.io/luxon/"&gt;luxon&lt;/a&gt; instead of Moment.js and its large locale defaults). &lt;/li&gt;
&lt;li&gt; Be very cautious when using &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt; and inline Redux stores for managing application state. The redux state often inlined in HTML to “hydrate” a page can often bloat up the size of your responses.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Adapt to avoid loading heavy resources on slow connections
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tip: You may be interested in &lt;a href="https://dev.to/addyosmani/adaptive-loading-improving-web-performance-on-low-end-devices-1m69"&gt;adaptive loading - improving web performance on low-end devices&lt;/a&gt; for more on this topic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0Wkn1dm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/28y3swalphaqgulruopb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0Wkn1dm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/28y3swalphaqgulruopb.png" alt="adaptive loading"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/adaptive-serving-based-on-network-quality/"&gt;Adaptive Loading&lt;/a&gt; is a technique which “adapts” resources being served to a user based on their &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType"&gt;effective connection type&lt;/a&gt; (ECT) - available to supported browsers via the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API"&gt;Network Information API&lt;/a&gt;. Adaptive serving allows sites to ensure users on slow connections can get “a” version of the experience, even if at a lower-fidelity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effectiveType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note: Even on "fast" 4G, a user can experience slow network speeds as you may have experienced on coffee-shop or conference WiFi.&lt;/p&gt;

&lt;p&gt;A concrete example where adaptive serving could be used is in a product item component. A user on a slow connection may only be served a compressed version of the product image, while a user on a faster connection may load a high-quality image and enhancements requiring more JavaScript - like the ability to zoom into the product image or view a carousel of different product image perspectives.&lt;/p&gt;

&lt;p&gt;With feature phones a slow connection may not necessarily be the biggest obstacle. Slow CPU and low memory are more likely to affect the users experience even when they are on a decent 4G connection. Although we don’t yet have access to CPU reporting, &lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints/"&gt;Client Hints&lt;/a&gt; provide approximates for Device Memory, Viewport-width, Device Pixel Ratio, network information and other signals which could be used to craft a more granular serving strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be respectful of users data plans with the Save-Data header
&lt;/h2&gt;

&lt;p&gt;Chrome for Android has a feature called &lt;a href="https://blog.chromium.org/2019/04/data-saver-is-now-lite-mode.html"&gt;Lite Mode (Data Saver)&lt;/a&gt;, allowing data-conscious users to opt-in to the browser automatically optimizing resources to improve how quickly pages load. Optimizations include more heavily compressing images, deferring non-critical resources or server-rendering previews of the page. More on Chrome’s efforts can be found in our &lt;a href="https://blog.chromium.org/2019/03/chrome-lite-pages-for-faster-leaner.html"&gt;Lite Pages&lt;/a&gt; blog post.&lt;/p&gt;

&lt;p&gt;When users enable a data-saving mode (like Lite Mode) in supported browsers, it will append the &lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data/"&gt;Save-Data request header&lt;/a&gt; to all HTTP and HTTPS requests. Application developers can check if this hint is enabled in JavaScript to deliver an optimized experience to users who have Lite Mode on (e.g conditionally turning off heavy features). The following snippet of code can be used to check for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Implement data saving operations here.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--08W0EvR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/m3er37u2u29lbflnh4kn.png" class="article-body-image-wrapper"&gt;&lt;img width="240" src="https://res.cloudinary.com/practicaldev/image/fetch/s--08W0EvR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/m3er37u2u29lbflnh4kn.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Although your feature phone may support Chrome, this does not guarantee that Lite Mode (Data Saver) is available. It is best to treat availability of this feature as speculative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offload costly app logic and state handling to Web Workers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tip: Do read &lt;a href="https://web.dev/load-faster-like-proxx/"&gt;Techniques to make a web app load fast, even on a feature phone&lt;/a&gt; by Surma. It's excellent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Besides running JavaScript, the browser’s main thread has other responsibilities like page layout, painting pixels on the screen, and tracking user interactions. Long-running, complex JavaScript, could end up blocking these other tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"&gt;Web Workers&lt;/a&gt; allow JavaScript code to run in the “background” without blocking the main thread. They should be used to keep the main thread free from costly JavaScript overhead such as complex app logic or state management services. The main thread and the worker thread communicate using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage"&gt;postMessage()&lt;/a&gt; function and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage"&gt;onmessage&lt;/a&gt; handler. The postMessage function allows the sender to send a single parameter which can be any value or object. Libraries like &lt;a href="https://github.com/GoogleChromeLabs/comlink"&gt;Comlink&lt;/a&gt; reduce the friction for using workers in your apps.&lt;/p&gt;

&lt;p&gt;Surma's &lt;a href="https://dassur.ma/things/when-workers/"&gt;case study&lt;/a&gt; with and without worker threads for &lt;a href="https://proxx.app/"&gt;Proxx&lt;/a&gt; is a great read - it was observed that without worker threads the app was frozen for 6.6 seconds on a Nokia 2 (1GB RAM/1.3 GHz quad-core processor). With worker threads however the response time was 48ms for the same action/event. Thus If you have CPU intensive logic, it's worth measuring whether moving this to a worker thread is worthwhile for your use-case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize Images
&lt;/h2&gt;

&lt;p&gt;Images often consume a lot of data. They also take time to decode, especially on lower-end devices, which is a good reason it’s important to ensure images are correctly sized and compressed when being delivered to a feature phone.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://web.dev/use-imagemin-to-compress-images/"&gt;Compress images&lt;/a&gt; using a tool like Imagemin to generate images which are smaller in size without a perceivable effect on quality. &lt;/li&gt;
&lt;li&gt; &lt;a href="https://web.dev/replace-gifs-with-videos/"&gt;Replace animated GIF’s with videos&lt;/a&gt; which load much faster. Even then, really consider how much you need heavy multimedia on low-end devices.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://web.dev/use-lazysizes-to-lazyload-images/"&gt;Lazy load images&lt;/a&gt; if possible but ensure JavaScript required for this is not too heavy. The new native &lt;a href="https://addyosmani.com/blog/lazy-loading/"&gt;&lt;code&gt;loading&lt;/code&gt; attribute&lt;/a&gt; can help here.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://web.dev/serve-responsive-images/"&gt;Serve responsive images&lt;/a&gt; with correct dimensions by creating multiple versions of images and serving the most appropriate for the user’s viewport&lt;/li&gt;
&lt;li&gt; &lt;a href="https://web.dev/serve-images-with-correct-dimensions/"&gt;Serve images with the correct dimensions for the screen&lt;/a&gt;. Loading low-fidelity, low resolution images on low-end devices can ensure that images are decoded faster.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Detecting screen-size
&lt;/h2&gt;

&lt;p&gt;Many current smart feature phones have a QVGA screen size in which the resolution is 320px horizontally by 240px vertically (320 x 240). If you need to detect screen size on page load (e.g to toggle certain features on/off or use adaptive loading), you could use a snippet like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isFeaturePhone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is similar to the &lt;a href="https://github.com/GoogleChromeLabs/proxx/blob/f54224c6aa6de7d5a6011136f9228db2b49ea858/src/main/utils/static-display.ts"&gt;approach&lt;/a&gt; Proxx uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emulate a feature phone in Chrome DevTools
&lt;/h2&gt;

&lt;p&gt;If building for a feature phone, it's highly recommended to pick up a cheap handset for real-world testing. &lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---dDX9L00--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/D6aGb2ZX4AEC7Op.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yh1m1R8Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/593793041816629248/yKbOT56n_normal.jpg" alt="Mariko Kosaka profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Mariko Kosaka
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @kosamari
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8bbACBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-99c56e7c338b4d5c17d78f658882ddf18b0bbde5b3f42f84e7964689e7e8fb15.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      PSA: if you like to test if your website works on KaiOS &amp;amp; if you live in US, a trip to BestBuy and $15 will get you this device for testing. 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      00:45 AM - 13 May 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1127736577763966980" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1127736577763966980" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      4
      &lt;a href="https://twitter.com/intent/like?tweet_id=1127736577763966980" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      47
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;If you'd like to emulate a feature phone (e.g a Reliance Jio KaiOS device) in Chrome DevTools, here's how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Chrome DevTools&lt;/li&gt;
&lt;li&gt;Toggle the Device Toolbar&lt;/li&gt;
&lt;li&gt;Device dropdown &amp;gt; Edit ... &amp;gt; Add a custom device&lt;/li&gt;
&lt;li&gt;Name: KaiOS (or customize as needed)&lt;/li&gt;
&lt;li&gt;Width: 240, Height: 320&lt;/li&gt;
&lt;li&gt;UA:
&lt;code&gt;Mozilla/5.0 (Mobile; LYF/F90M/LYF-F90M-000-02-23-181217; Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5 YouTube/1.66.51.J&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;li&gt;(Optionally) customize CPU throttling as needed, however note that this can never be as accurate as testing on a real-device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gnOQ8J_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pcyv85vgo4x0bps4wqsa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gnOQ8J_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pcyv85vgo4x0bps4wqsa.png" alt="devtools device mode showing how to customize an entry to kaios"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;It's possible to deliver a delightful experience to all users, regardless of where they are. This does require extra care as not all mobile hardware is equal.&lt;/p&gt;

&lt;p&gt;The more affordable a phone handset is, the higher the chance it may contain a slow CPU. Given JavaScript performance depends on download and execution time, do think about how you're delivering your experience. &lt;/p&gt;

&lt;p&gt;While this matters for smart phones, it matters even more for feature phones.&lt;/p&gt;

</description>
      <category>featurephone</category>
      <category>webperf</category>
      <category>nbu</category>
      <category>emergingmarkets</category>
    </item>
    <item>
      <title>Accessibility Tips for Web Developers</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Wed, 04 Dec 2019 20:28:54 +0000</pubDate>
      <link>https://forem.com/addyosmani/accessibility-tips-for-web-developers-4cn0</link>
      <guid>https://forem.com/addyosmani/accessibility-tips-for-web-developers-4cn0</guid>
      <description>&lt;p&gt;&lt;strong&gt;It's awesome to build sites that are inclusive and accessible to everyone. There are at least six key areas of disability we can optimize for: visual, hearing, mobility, cognition, speech and neural. Many tools and resources can help here, even if you're totally new to web accessibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over 1 billion people live with some form of disability. You might have been in a loud room at some point trying to hear the conversation around you or in a low-lighting condition trying to find something in the dark. Do you remember the frustration you felt with that circumstance? Now, imagine if that temporary condition was permanent. How different would your experience on the web be? &lt;/p&gt;

&lt;p&gt;To be accessible, sites need to work across multiple devices with varying screen-sizes and different kinds of input, such as screen readers. Moreover, sites should be usable by the broadest group of users, including those with disabilities. Here are a sample of just a few disabilities your users may have:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;&lt;strong&gt;Vision&lt;/strong&gt;&lt;/th&gt;
    &lt;th&gt;&lt;strong&gt;Hearing&lt;/strong&gt;&lt;/th&gt;
    &lt;th&gt;&lt;strong&gt;Mobility&lt;/strong&gt;&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;- Low vision&lt;br&gt;- Blind&lt;br&gt;- Color blind&lt;br&gt;- Cataracts&lt;br&gt;- Sun glare&lt;/td&gt;
    &lt;td&gt;- Hard of hearing&lt;br&gt;- Deaf&lt;br&gt;- Noise&lt;br&gt;- Ear infection&lt;/td&gt;
    &lt;td&gt;- Broken arm&lt;br&gt;- Spinal cord injury&lt;br&gt;- Limited dexterity&lt;br&gt;- Hands full&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;strong&gt;Cognitive&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;&lt;strong&gt;Speech&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;&lt;strong&gt;Neural&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;- Learning disabilities&lt;br&gt;- Sleepy or distracted&lt;br&gt;- Migraine&lt;br&gt;- Autism&lt;br&gt;- Seizure&lt;/td&gt;
    &lt;td&gt;- Ambient noise&lt;br&gt;- Sore throat&lt;br&gt;- Speech impediment&lt;br&gt;- Unable to speak&lt;/td&gt;
    &lt;td&gt;- Depression&lt;br&gt;- PTSD&lt;br&gt;- Bipolar&lt;br&gt;- Anxiety&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Visual issues&lt;/strong&gt; range from an inability to distinguish colors to no vision at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ensure a minimum &lt;a href="http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast" rel="noopener noreferrer"&gt;contrast ratio threshold&lt;/a&gt; is met for text content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid communicating information &lt;a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-without-color" rel="noopener noreferrer"&gt;using solely color&lt;/a&gt; and ensure that all text is &lt;a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-scale" rel="noopener noreferrer"&gt;resizable&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure all user interface components can be used with assistive technologies such as screen readers, magnifiers and braille displays. This entails ensuring that UI components are marked up such that accessibility APIs can programmatically determine the &lt;em&gt;role&lt;/em&gt;, &lt;em&gt;state&lt;/em&gt;, &lt;em&gt;value&lt;/em&gt; and &lt;em&gt;title&lt;/em&gt; of any element.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Inspect element in Chrome, Edge and Firefox DevTools displays a tooltip of CSS properties that includes a quick check for color contrast ratio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6ekpwoar92j0aumu5697.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6ekpwoar92j0aumu5697.png" alt="Hovering over an element you are inspecting will display a summary including color contrast ratio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I personally live with low-vision and am embarrassed to say, I'm that person that always zooms in on sites, their DevTools and terminal. While supporting zoom is almost never at the top of anyone's list, optimizing for low-vision users is always appreciated... 🤓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hearing issues&lt;/strong&gt; mean a user may have issues hearing sound emitted from a page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make the content understandable using &lt;a href="http://www.w3.org/TR/WCAG20/#media-equiv-av-only-alt" rel="noopener noreferrer"&gt;text alternatives&lt;/a&gt; for all content that is not strictly text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure you test that your UI components are still functional &lt;a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#content-structure-separation-understanding" rel="noopener noreferrer"&gt;without sound&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2zedl4bkcdn0e5ifd0nv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2zedl4bkcdn0e5ifd0nv.jpg" alt="VoiceOver for Mac being used to navigate dev.to in Safari"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobility issues&lt;/strong&gt; can include the inability to operate a mouse, a keyboard or touch-screen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make the content of your UI components &lt;a href="http://www.w3.org/TR/wai-aria-practices/#keyboard" rel="noopener noreferrer"&gt;functionally accessible from a keyboard&lt;/a&gt; for any actions one would otherwise use a mouse for.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure pages are correctly marked up for assistive technologies; these users may use technologies such as voice control software and physical switch controls, which tend to use the same APIs as other assistive technologies like screen readers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cognitive issues&lt;/strong&gt; mean a user may require assistive technologies to help them with reading text, so it’s important to ensure text alternatives exist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be mindful when using animations. Avoid a visual presentation that is &lt;a href="http://www.w3.org/TR/WCAG20/#time-limits" rel="noopener noreferrer"&gt;repetitive&lt;/a&gt; or flashing as this can cause some users &lt;a href="http://www.w3.org/TR/WCAG20/#seizure" rel="noopener noreferrer"&gt;issues&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developers.google.com/web/updates/2019/03/prefers-reduced-motion#too_much_motion_in_real_life_and_on_the_web" rel="noopener noreferrer"&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; CSS media query allows you to limit animations and autoplaying videos for users who prefer reduced motion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
If the user expressed a preference for reduced motion, don't use animations on buttons.
*/&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Avoid interactions that are timing-based.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This may seem like a lot of bases to cover, but we’ll walk through the process for assessing and then improving the accessibility of your UI components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Some great &lt;a href="https://accessibility.blog.gov.uk/2016/09/02/dos-and-donts-on-designing-for-accessibility/" rel="noopener noreferrer"&gt;accessibility do's and don'ts digital posters&lt;/a&gt; are available by the Gov.uk accessibility team for spreading awareness of best practices in your team:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi7dpk0nka3n8f35ftoju.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi7dpk0nka3n8f35ftoju.jpg" alt="Thumbnail of accessibility posters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Are your UI components accessible?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Summary (tl;dr)
&lt;/h3&gt;

&lt;p&gt;When auditing your page's UI components for accessibility, ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can you use your UI component with the keyboard only?&lt;/strong&gt; Does it manage to focus and avoid focus traps? Can it respond to the appropriate keyboard events?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can you use your UI component with a screen reader?&lt;/strong&gt; Have you provided text alternatives for any information which is presented visually? Have you added semantic information using ARIA?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can your UI component work without sound?&lt;/strong&gt; Turn off your speakers and go through your use cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can it work without color?&lt;/strong&gt; Ensure your UI component can be used by someone who cannot see colors. A helpful tool for simulating color blindness is a Chrome extension called &lt;a href="https://chrome.google.com/webstore/detail/see/dkihcccbkkakkbpikjmpnbamkgbjfdcn" rel="noopener noreferrer"&gt;SEE&lt;/a&gt;, (try all four forms of color blindness simulation available). You may also be interested in the &lt;a href="https://chrome.google.com/webstore/detail/chrome-daltonize/efeladnkafmoofnbagdbfaieabmejfcf" rel="noopener noreferrer"&gt;Daltonize&lt;/a&gt; extension which is similarly useful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can your UI component work with high-contrast mode enabled?&lt;/strong&gt; All modern operating systems support a high contrast mode. &lt;a href="https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph?hl=en" rel="noopener noreferrer"&gt;High Contrast&lt;/a&gt; is a Chrome extension available that can help here.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Native controls (such as &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;) have accessibility built-in by the browser. They are focusable using the tab key, respond to keyboard events (like Enter, space and arrow keys), and have semantic roles, states and properties used by accessibility tools. The default styling should also meet the accessibility requirements listed above.&lt;/p&gt;

&lt;p&gt;Custom UI components (with the exception of components that extend native elements like &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;) do not have any built-in functionality, including accessibility, so this needs to be provided by you. A good place to start when implementing accessibility is to compare your components to an analogous native element (or a combination of several native elements, depending on how complex your component is).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Most browser DevTools support inspecting the accessibility tree of a page. In Chrome, this is available via the Accessibility tab in the Elements panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcv4pk0aexiy9fcnrant9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcv4pk0aexiy9fcnrant9.png" alt="Chrome DevTools accessibility tree shown in the Elements panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flh6rcrcf9vfs5gcuplkb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flh6rcrcf9vfs5gcuplkb.jpg" alt="FireFox DevTools accessibility tree shown when accessibility features are turned on in the Accessibility panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firefox also has an Accessibility panel and Safari exposes this information in the Element's panel Node tab.&lt;/p&gt;

&lt;p&gt;The following is a list of questions you can ask yourself when attempting to make your UI components more accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can your UI component be used with the keyboard alone?
&lt;/h2&gt;

&lt;p&gt;Ideally, ensure that all functionality in your UI component can be reached by a keyboard. During your UX design, think about how you would use your element with the keyboard alone, and figure out a consistent set of keyboard interactions.&lt;/p&gt;

&lt;p&gt;Firstly, ensure that you have a sensible focus target for each component. For example, a complex component like a menu may be one focus target within a page, but should then manage focus within itself so that the active menu item always takes focus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fapgfbqfe0g6ugxg7r6cn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fapgfbqfe0g6ugxg7r6cn.png" alt="Managing focus within a complex element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managing focus within a complex element&lt;/p&gt;

&lt;h3&gt;
  
  
  Using tabindex
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;tabindex&lt;/code&gt; attribute allows elements / UI components to be focused using the keyboard. Keyboard-only and assistive technology users both need to be able to place keyboard focus on elements in order to interact with them. Native interactive elements are implicitly focusable, so they don’t need a tabindex attribute unless we wish to change their position in the tab order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are three types of tabindex values:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tabindex="0"&lt;/strong&gt; is the most common, and will place the element in the “natural” tab order (defined by the DOM order).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;a tabindex value greater than 0&lt;/strong&gt; will place the element in a &lt;em&gt;manual&lt;/em&gt; tab order — all elements in the page with a positive tabindex value will be visited in numerical order before elements in the natural tab order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;a tabindex value equal to -1&lt;/strong&gt; will cause the element to be &lt;em&gt;programmatically&lt;/em&gt; focusable, but not in the tab order.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For custom UI components, always use &lt;code&gt;tabindex&lt;/code&gt; values of 0 or -1, as you won’t be able to determine the order of elements on a given page ahead of time — and even if we did, they may be subject to change. A &lt;code&gt;tabindex&lt;/code&gt; value of -1 is particularly useful for managing focus within complex components as described above.&lt;/p&gt;

&lt;p&gt;Also ensure that focus is always visible, whether by allowing the default focus ring style, or applying a discernible focus style. Remember not to trap the keyboard user — focus should be able to be moved away from an element using only the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You may also be interested in the roving &lt;code&gt;tabindex&lt;/code&gt; or &lt;code&gt;aria-activedescendant&lt;/code&gt; approaches, covered over on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#Technique_1_Roving_tabindex" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using autofocus
&lt;/h3&gt;

&lt;p&gt;The HTML &lt;code&gt;autofocus&lt;/code&gt; attribute allows an author to specify that a particular element should automatically take focus when the page is loaded. It is already supported on &lt;a href="https://html.spec.whatwg.org/multipage/forms.html#association-of-controls-and-forms" rel="noopener noreferrer"&gt;all web form controls&lt;/a&gt;, including . To &lt;code&gt;autofocus&lt;/code&gt; elements in your own custom UI components, call the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.focus" rel="noopener noreferrer"&gt;focus()&lt;/a&gt; method supported on all HTML elements that can be focused (e.g &lt;code&gt;document.querySelector('myButton').focus()&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding keyboard interaction
&lt;/h3&gt;

&lt;p&gt;Once your UI component is focusable, try to provide a good keyboard interaction story when a component is focused, by handling appropriate keyboard events — for example, allow the user to use arrow keys to select menu options, and space or enter to activate buttons. The ARIA &lt;a href="http://www.w3.org/TR/wai-aria-practices/#aria_ex" rel="noopener noreferrer"&gt;design patterns guide&lt;/a&gt; provides some guidance here.&lt;/p&gt;

&lt;p&gt;Finally, ensure that your keyboard shortcuts are discoverable. For example, a common practice is to have a keyboard shortcut legend (on-screen text) to inform the user that shortcuts exist. For example, "Press ? for keyboard shortcuts". Alternatively a hint such a tooltip could be used to inform the user about the shortcut existing.&lt;/p&gt;

&lt;p&gt;The importance of managing focus cannot be understated. One example is a navigation drawer. If adding a UI component to the page you need to direct focus to an element inside of it otherwise users may have to tab through the entire page to get there. This can be a frustrating experience, so be sure to test focus for &lt;em&gt;all&lt;/em&gt; keyboard navigable components in your page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can use &lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; to automate running keyboard accessibility tests for toggling UI states. &lt;a href="https://medium.com/walkme-engineering/web-accessibility-testing-d499a7f7a032" rel="noopener noreferrer"&gt;WalkMe Engineering&lt;/a&gt; have a great guide on this I recommend reading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F85qx2xen4yyfji46asku.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F85qx2xen4yyfji46asku.gif" alt="Expand and collapse category with spacebar key"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example for expanding and collapsing a category with the spacebar key&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.category`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// verify tabIndex, role and focus&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`role`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`button`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`tabindex`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`0`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// verify aria-expanded = false&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`aria-expanded`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`false`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// toggle category by press space&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Space&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// verify aria-expanded = true&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`aria-expanded`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`true`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Can you use your UI component with a screen reader?
&lt;/h2&gt;

&lt;p&gt;Around 1–2% of users will be using a screen reader. Can you determine all important information and interact with the component using the screen reader and keyboard alone?&lt;/p&gt;

&lt;p&gt;The following questions should help guide you in addressing screen reader accessibility:&lt;/p&gt;

&lt;h3&gt;
  
  
  Do all components and images have meaningful text alternatives?
&lt;/h3&gt;

&lt;p&gt;Wherever information about the &lt;em&gt;name&lt;/em&gt; or &lt;em&gt;purpose&lt;/em&gt; of an interactive component is conveyed visually, an accessible text alternative needs to be provided.&lt;/p&gt;

&lt;p&gt;For example, if your &lt;code&gt;&amp;lt;fancy-menu&amp;gt;&lt;/code&gt; UI component only displays an icon such as a Settings menu icon to indicate that it is a settings menu, it needs an accessible text alternative such as "settings", which conveys the same information. Depending on context, this may use an alt attribute, an aria-label attribute, an aria-labelledby attribute, or plain text in the Shadow DOM. You can find general technical tips in &lt;a href="http://webaim.org/resources/quickref/" rel="noopener noreferrer"&gt;WebAIM Quick Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Any UI component which displays an image should provide a mechanism for providing alternative text for that image, analogous to the alt attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do your components provide semantic information?
&lt;/h3&gt;

&lt;p&gt;Assistive technology conveys semantic information which is otherwise expressed to sighted users via visual cues such as formatting, cursor style, or position. Native elements have this semantic information built-in by the browser, but for custom components you need to use &lt;a href="http://www.w3.org/WAI/PF/aria/" rel="noopener noreferrer"&gt;ARIA&lt;/a&gt; to add this information in.&lt;/p&gt;

&lt;p&gt;As a rule of thumb, any component which listens to a mouse click or hover event should not only have some kind of keyboard event listener, but also an ARIA role and potentially ARIA states and attributes.&lt;/p&gt;

&lt;p&gt;For example, a custom &lt;code&gt;&amp;lt;fancy-slider&amp;gt;&lt;/code&gt; UI component might take an ARIA role of slider, which has some related ARIA attributes: aria-valuenow, aria-valuemin and aria-valuemax. By binding these attributes to the relevant properties on your custom component, you can allow users of assistive technology to interact with the element and change its value, and even cause the visual presentation of the element to change accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fowe8tv4ligp8kildlab6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fowe8tv4ligp8kildlab6.png" alt="A range slider component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A range slider component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fancy-slider&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"slider"&lt;/span&gt; &lt;span class="na"&gt;aria-valuemin=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;aria-valuemax=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; 
&lt;span class="na"&gt;aria-valuenow=&lt;/span&gt;&lt;span class="s"&gt;"2.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/fancy-slider&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Can users understand everything without relying on color?
&lt;/h3&gt;

&lt;p&gt;Color shouldn’t be used as the only means of conveying information, such as indicating a status, prompting for a response or distinguishing a visual custom component. For example, if you created an &lt;code&gt;&amp;lt;fancy-map&amp;gt;&lt;/code&gt; component using color to distinguish between heavy, moderate and light traffic, an alternative means of distinguishing traffic levels should also be made available: one solution might be to hover over an element to display information in a tooltip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there sufficient contrast between the text/images and the background?
&lt;/h3&gt;

&lt;p&gt;Any text content displayed in your component should meet the &lt;a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast" rel="noopener noreferrer"&gt;minimum (AA) contrast bar&lt;/a&gt;. Consider providing a high-contrast theme which meets the &lt;a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast7" rel="noopener noreferrer"&gt;higher (AAA) bar&lt;/a&gt;, and also ensure that user agent style sheets can be applied if users require extreme contrast or different colors. You can use this &lt;a href="http://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;Color Contrast Checker&lt;/a&gt; as an aid when doing design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the moving or flashing content in your components stoppable and safe?
&lt;/h3&gt;

&lt;p&gt;Content that moves, scrolls or blinks that lasts for anything more than 5 seconds should be able to be paused, stopped or hidden. In general, try to flash no more than three times per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Tooling
&lt;/h2&gt;

&lt;p&gt;A number of tools are available that can assist with debugging the accessibility of your visual components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://www.deque.com/products/axe/" rel="noopener noreferrer"&gt;Axe&lt;/a&gt; provides automated accessibility testing for your framework or browser of choice. &lt;a href="https://www.deque.com/blog/axe-and-attest-integration-puppeteer/" rel="noopener noreferrer"&gt;Axe Puppeteer&lt;/a&gt; can be used for writing automated accessibility tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://developers.google.com/web/tools/lighthouse" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; Accessibility audits provide helpful insights for discovering common accessibility issues. The accessibility score is a weighted average of all accessibility audits, based on &lt;a href="https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md" rel="noopener noreferrer"&gt;Axe user impact assessments&lt;/a&gt;. For monitoring accessibility via continuous integration, see &lt;a href="https://github.com/GoogleChrome/lighthouse-ci" rel="noopener noreferrer"&gt;Lighthouse CI&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftl7y0w387uxnmxa9duuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftl7y0w387uxnmxa9duuk.png" alt="lighthouse accessibility audits"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://tenon.io/" rel="noopener noreferrer"&gt;Tenon.io&lt;/a&gt; is useful for testing common accessibility problems. Tenon has strong integration support across build tools, browsers (via extensions) and even text editors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are many library and framework specific tools for highlighting accessibility issues with components. e.g &lt;a href="https://web.dev/accessibility-auditing-react/" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt; covers using &lt;a href="https://www.npmjs.com/package/eslint-plugin-jsx-a11y" rel="noopener noreferrer"&gt;eslint-plugin-jsx-a11y&lt;/a&gt; to highlight accessibility issues for React components in your editor:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftqy8b3sgoan9lq5rjs3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftqy8b3sgoan9lq5rjs3z.png" alt="ESLint accessibility checks for JSX being run from inside an editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use Angular, &lt;a href="https://web.dev/accessible-angular-with-codelyzer/" rel="noopener noreferrer"&gt;codelyzer&lt;/a&gt; provides in-editor accessibility audits too: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyqpg6wykxv6d0if8w6kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyqpg6wykxv6d0if8w6kq.png" alt="Codelyzer highlighting accessibility issues in an Angular application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can examine the way that assistive technologies see web content by using &lt;a href="https://developer.apple.com/library/mac/documentation/Accessibility/Conceptual/AccessibilityMacOSX/OSXAXTesting/OSXAXTestingApps.html#//apple_ref/doc/uid/TP40001078-CH210-TPXREF101" rel="noopener noreferrer"&gt;Accessibility Inspector&lt;/a&gt; (Mac), or &lt;a href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd373661(v=vs.85).aspx" rel="noopener noreferrer"&gt;Windows Automation API Testing Tools&lt;/a&gt; and &lt;a href="http://accessibility.linuxfoundation.org/a11yweb/util/accprobe/" rel="noopener noreferrer"&gt;AccProbe&lt;/a&gt; (Windows). Additionally you can see the full accessibility tree that Chrome creates by navigating to &lt;em&gt;chrome://accessibility&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The best way to test for screen reader support on a Mac is using the VoiceOver utility. You can use ⌘F5 to enable/disable, Ctrl+Option ←→ to move through the page and Ctrl+Shift+Option + ↑↓ to move up/down tree. For more detailed instructions, see the &lt;a href="http://www.apple.com/voiceover/info/guide/_1131.html" rel="noopener noreferrer"&gt;full list of VoiceOver commands&lt;/a&gt; and the &lt;a href="http://www.apple.com/voiceover/info/guide/_1131.html#vo27972" rel="noopener noreferrer"&gt;list of VoiceOver Web commands&lt;/a&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://khan.github.io/tota11y/" rel="noopener noreferrer"&gt;tota11y&lt;/a&gt; is a useful visualiser for assistive technology issues built by Khan Academy. It’s a script that adds a button to your document that triggers several plugins for annotating things like insufficient contrast ratio and other a11y violations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On Windows, &lt;a href="http://www.nvaccess.org/" rel="noopener noreferrer"&gt;NVDA&lt;/a&gt; is a free, open source screen reader which is fully featured and rapidly gaining in popularity. However, note that it has a much steeper learning curve for sighted users than VoiceOver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ChromeLens helps develop for the visually impaired. It’s also got great support for visualising keyboard navigation paths &lt;a href="http://chromelens.xyz/" rel="noopener noreferrer"&gt;http://chromelens.xyz/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr9p7c72h93y6qr7wtslf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr9p7c72h93y6qr7wtslf.jpg" alt="ChromeLens Chrome extension displaying navigation paths for keyboard accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.chromevox.com/" rel="noopener noreferrer"&gt;ChromeVox&lt;/a&gt; is a screen reader which is available as a Chrome extension, and built in on ChromeOS devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;We still have a long way to go improving accessibility on the web. Per the &lt;a href="https://almanac.httparchive.org/en/2019/accessibility" rel="noopener noreferrer"&gt;Web Almanac&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 out of every 5 sites have text which easily blends into the background, making it unreadable.&lt;/li&gt;
&lt;li&gt;49.91% of pages still fail to provide alt attributes for some of their images&lt;/li&gt;
&lt;li&gt;Only 24% of pages that use buttons or links include textual labels with these controls.&lt;/li&gt;
&lt;li&gt;Only 22.33% of pages provide labels for all their form inputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about accessibility fundamentals to help us improve the above, I recommend the &lt;a href="https://web.dev/accessible/" rel="noopener noreferrer"&gt;Accessible to all&lt;/a&gt; docs on web.dev. There's much we can do to build experiences that are more inclusive of everyone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3nrnacb9zarrmfeceorw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3nrnacb9zarrmfeceorw.jpg" alt="web.dev"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>mobility</category>
      <category>inclusion</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Adaptive Loading - Improving Web Performance on low-end devices</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Mon, 18 Nov 2019 19:56:47 +0000</pubDate>
      <link>https://forem.com/addyosmani/adaptive-loading-improving-web-performance-on-low-end-devices-1m69</link>
      <guid>https://forem.com/addyosmani/adaptive-loading-improving-web-performance-on-low-end-devices-1m69</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=puUPpVrIRkc" rel="noopener noreferrer"&gt;Adaptive Loading&lt;/a&gt;: Do not just respond based on screen size, adapt based on actual device hardware.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any user can have a slow experience&lt;/strong&gt;. In a world with widely varying device capabilities, &lt;strong&gt;a "one-size" fits all experience may not always work&lt;/strong&gt;. Sites that delight users on high-end devices can be &lt;a href="https://v8.dev/blog/cost-of-javascript-2019" rel="noopener noreferrer"&gt;unusable&lt;/a&gt; on low-end ones, particularly on median mobile and desktop hardware and in emerging markets. What if we could adapt how we deliver pages to better cater for our user's constraints? 🤔&lt;/p&gt;

&lt;p&gt;In our &lt;a href="https://www.youtube.com/watch?v=puUPpVrIRkc" rel="noopener noreferrer"&gt;Chrome Dev Summit talk&lt;/a&gt;, Facebook's Nate Schloss and I talk about &lt;strong&gt;Adaptive Loading&lt;/strong&gt; - this pattern ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivers a fast core experience to all users (including low-end devices)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressively adds high-end-only features, if a user's network and hardware can handle it&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows users to get an experience best suited to their constraints.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/puUPpVrIRkc"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The use-cases adaptive loading unlocks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serving low-quality images &amp;amp; videos on slow networks&lt;/li&gt;
&lt;li&gt;Loading non-critical JavaScript for interactivity only on fast CPUs&lt;/li&gt;
&lt;li&gt;Throttling the frame-rate of Animations on low-end devices&lt;/li&gt;
&lt;li&gt;Avoiding computationally heavy operations on low-end devices&lt;/li&gt;
&lt;li&gt;Block 3rd-party scripts on slower devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnzuf1r1gw4v6p91jsv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnzuf1r1gw4v6p91jsv5.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a number of signals we can use for Adaptive Loading, including: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt; - for fine-tuning data transfer to use less bandwidth (via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API" rel="noopener noreferrer"&gt;&lt;code&gt;navigator.connection.effectiveType&lt;/code&gt;&lt;/a&gt;). We can also leverage the user's Data Saver preferences (via &lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data#detecting_the_save-data_setting" rel="noopener noreferrer"&gt;&lt;code&gt;navigator.connection.saveData&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; - for reducing memory consumption on low-end devices (via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory" rel="noopener noreferrer"&gt;&lt;code&gt;navigator.deviceMemory&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU core count&lt;/strong&gt; - for limiting costly JavaScript execution and reducing CPU intensive logic when a device can't handle it well (via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency" rel="noopener noreferrer"&gt;&lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt;&lt;/a&gt;). This is because JavaScript execution is &lt;a href="https://www.youtube.com/watch?v=puUPpVrIRkc" rel="noopener noreferrer"&gt;CPU bound&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our talk, we cover real-world examples of these ideas being used in sites such as Facebook, eBay, Tinder and others. Check out &lt;a href="https://youtu.be/puUPpVrIRkc?t=1443" rel="noopener noreferrer"&gt;24mins in&lt;/a&gt; where Nate walks through how Facebook uses some of these ideas in production, via device grouping:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofuluzjub1qhlr7qutp8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofuluzjub1qhlr7qutp8.jpg" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also released a new (experimental) set of &lt;a href="https://github.com/GoogleChromeLabs/react-adaptive-hooks" rel="noopener noreferrer"&gt;React Hooks &amp;amp; Utilities&lt;/a&gt; for adding adaptive-loading techniques to your React apps. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/GoogleChromeLabs" rel="noopener noreferrer"&gt;
        GoogleChromeLabs
      &lt;/a&gt; / &lt;a href="https://github.com/GoogleChromeLabs/react-adaptive-hooks" rel="noopener noreferrer"&gt;
        react-adaptive-hooks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Deliver experiences best suited to a user's device and network constraints
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;React Adaptive Loading Hooks &amp;amp; Utilities · &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/821e3aec7d467f46331b89f1e6dd24ec0639b21b61fd29750d6cef2b48038d2d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f476f6f676c654368726f6d654c6162732f72656163742d61646170746976652d686f6f6b732e737667"&gt;&lt;img src="https://camo.githubusercontent.com/821e3aec7d467f46331b89f1e6dd24ec0639b21b61fd29750d6cef2b48038d2d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f476f6f676c654368726f6d654c6162732f72656163742d61646170746976652d686f6f6b732e737667" alt=""&gt;&lt;/a&gt; &lt;a href="https://travis-ci.org/GoogleChromeLabs/react-adaptive-hooks" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/88aff1ba5b23897fa5f445370d332d746f292a305583d9c7cea536f48e063e09/68747470733a2f2f7472617669732d63692e6f72672f476f6f676c654368726f6d654c6162732f72656163742d61646170746976652d686f6f6b732e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/fe6179c0dab61aa43a51f22afdb58867ba185d5c0e9cd47439a2f3f46f22af64/68747470733a2f2f696d672e736869656c64732e696f2f62756e646c6570686f6269612f6d696e7a69702f72656163742d61646170746976652d686f6f6b73"&gt;&lt;img src="https://camo.githubusercontent.com/fe6179c0dab61aa43a51f22afdb58867ba185d5c0e9cd47439a2f3f46f22af64/68747470733a2f2f696d672e736869656c64732e696f2f62756e646c6570686f6269612f6d696e7a69702f72656163742d61646170746976652d686f6f6b73" alt="npm bundle size"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Deliver experiences best suited to a user's device and network constraints (experimental)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a suite of &lt;a href="https://reactjs.org/docs/hooks-overview.html" rel="nofollow noopener noreferrer"&gt;React Hooks&lt;/a&gt; and utilities for adaptive loading based on a user's:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType" rel="nofollow noopener noreferrer"&gt;Network via effective connection type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData" rel="nofollow noopener noreferrer"&gt;Data Saver preferences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory" rel="nofollow noopener noreferrer"&gt;Device memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency" rel="nofollow noopener noreferrer"&gt;Logical CPU cores&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Media_Capabilities_API" rel="nofollow noopener noreferrer"&gt;Media Capabilities API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can be used to add patterns for adaptive resource loading, data-fetching, code-splitting and capability toggling.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Objective&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Make it easier to target low-end devices while progressively adding high-end-only features on top. Using these hooks and utilities can help you give users a great experience best suited to their device and network constraints.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;npm i react-adaptive-hooks --save&lt;/code&gt; or &lt;code&gt;yarn add react-adaptive-hooks&lt;/code&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;You can import the hooks you wish to use as follows:&lt;/p&gt;
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;useNetworkStatus&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'react-adaptive-hooks/network'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;useSaveData&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'react-adaptive-hooks/save-data'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;useHardwareConcurrency&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'react-adaptive-hooks/hardware-concurrency'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;useMemoryStatus&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/GoogleChromeLabs/react-adaptive-hooks" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Our hooks/utilities include the &lt;code&gt;useNetworkStatus&lt;/code&gt; React hook for adapting based on network status (effective connection type):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useNetworkStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-adaptive-hooks/network&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;effectiveConnectionType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNetworkStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConnectionType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium-res.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high-res.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;muted&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;muted&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useSaveData&lt;/code&gt; utility for adapting based on the user's browser Data Saver preferences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSaveData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-adaptive-hooks/save-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSaveData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveData&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;muted&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and the &lt;code&gt;useHardwareConcurrency&lt;/code&gt; utility for adapting based on the number of logical CPU processor cores on the user's device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useHardwareConcurrency&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-adaptive-hooks/hardware-concurrency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;numberOfLogicalProcessors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useHardwareConcurrency&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;numberOfLogicalProcessors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;muted&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the future, we hope to see more examples of infrastructure that can automatically deliver the most optimal bundles of code based on a user's network &amp;amp; device constraints. Between &lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints" rel="noopener noreferrer"&gt;Client Hints&lt;/a&gt; and the client-side APIs used above, the building blocks to build something compelling in this space may already exist :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59ppfvze0dly5pk9001g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59ppfvze0dly5pk9001g.jpg" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We hope you'll find Adaptive Loading an interesting addition to your Progressive Enhancement toolbox. Check out our &lt;a href="https://www.youtube.com/watch?v=puUPpVrIRkc" rel="noopener noreferrer"&gt;talk&lt;/a&gt; to learn more :)&lt;/p&gt;

&lt;p&gt;Read more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleChromeLabs/react-adaptive-hooks" rel="noopener noreferrer"&gt;React Adaptive Loading hooks and utilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@roderickhsiao/sophisticated-adaptive-loading-strategies-7118341fcf91" rel="noopener noreferrer"&gt;Sophisticated Adaptive Loading Strategies&lt;/a&gt; from Tinder Web engineering&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netbasal.com/connection-aware-components-in-angular-3a66bb0bab6f" rel="noopener noreferrer"&gt;Connection-Aware Components in Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/vorillaz/serving-adaptive-components-using-the-network-information-api-lbo"&gt;Serving Adaptive Components Using the Network Information API&lt;/a&gt; with Vue and Web Components&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/adaptive-serving-based-on-network-quality/" rel="noopener noreferrer"&gt;Adaptive serving based on network quality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>react</category>
      <category>network</category>
    </item>
    <item>
      <title>Web Page Usability Matters</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Thu, 10 Jan 2019 17:44:23 +0000</pubDate>
      <link>https://forem.com/addyosmani/web-page-usability-matters-3aok</link>
      <guid>https://forem.com/addyosmani/web-page-usability-matters-3aok</guid>
      <description>&lt;p&gt;&lt;strong&gt;Users appreciate pages being usable and interactive soon after they're visually ready. UI interactions (scrolls, taps, clicks) can be delayed by script and other browser work so minimizing their impact can really help your users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may have heard there isn't a single metric that fully captures the "loading experience" of a web page. Loading a page is a progressive journey with four key moments to it: is it happening? Is it useful? is it usable? and is it delightful?. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9enrd4e7p4slrlgns6t1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9enrd4e7p4slrlgns6t1.png" alt="When did the user feel they could interact? When could they interact? Speed metrics illustrate First Paint, First Contentful Paint, Time to Interactive for a page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In terms of measurable metrics, these moments breakdown as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is it happening?&lt;/strong&gt;: Has the navigation started successfully? has the server started responding? Metric: &lt;a href="https://www.w3.org/TR/paint-timing/" rel="noopener noreferrer"&gt;First Paint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it useful?&lt;/strong&gt;: when you’ve painted text, an image or content that allows the user to derive value from the experience and engage with it. Metrics: &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint" rel="noopener noreferrer"&gt;First Contentful Paint&lt;/a&gt;, &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint" rel="noopener noreferrer"&gt;First Meaningful Paint&lt;/a&gt;, &lt;a href="https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index" rel="noopener noreferrer"&gt;Speed Index&lt;/a&gt; (lab)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it usable?&lt;/strong&gt;: when a user can start meaningfully interacting with the experience and have something happen (e.g tapping on a button). This can be critical as users can get disappointed if they try using UI that looks ready but isn't. Metrics: &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive" rel="noopener noreferrer"&gt;Time to Interactive&lt;/a&gt; (lab), &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/first-cpu-idle" rel="noopener noreferrer"&gt;First CPU Idle&lt;/a&gt;, &lt;a href="https://developers.google.com/web/updates/2018/05/first-input-delay" rel="noopener noreferrer"&gt;First Input Delay&lt;/a&gt; (field)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it delightful?&lt;/strong&gt;: delightfulness is about ensuring performance of the user experience remains consistent after page load. Can you smoothly scroll without janking? are animations smooth and running at 60fps? do other &lt;a href="https://calendar.perfplanet.com/2017/tracking-cpu-with-long-tasks-api/" rel="noopener noreferrer"&gt;Long Tasks&lt;/a&gt; block any of these from happening?. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2g63ozqm2x3wyce97c8a.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Time to Interactive as highlighted by Chrome DevTools and Lighthouse. The user sees content early on but the page is not interactive until much later." src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2g63ozqm2x3wyce97c8a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page usability matters. Single-page Apps are unlikely to be usable (able to quickly respond to user input) if they haven't completed loading the JavaScript needed to attach event handlers for the experience or are hogging the browser's main thread (similar to above). This is a reason why monitoring usability metrics can be important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do users care about page usability?
&lt;/h2&gt;

&lt;p&gt;In 2018, Akamai conducted a &lt;a href="https://speakerdeck.com/bluesmoon/ux-and-performance-metrics-that-matter-a062d37f-e6c7-4b8a-8399-472ec76bb75e" rel="noopener noreferrer"&gt;UX study&lt;/a&gt; into the impact of interactivity on &lt;a href="https://blog.fullstory.com/rage-clicks-turn-analytics-into-actionable-insights/" rel="noopener noreferrer"&gt;Rage Clicks&lt;/a&gt; using &lt;a href="https://www.akamai.com/us/en/products/web-performance/mpulse-real-user-monitoring.jsp" rel="noopener noreferrer"&gt;mPulse&lt;/a&gt;. Rage Clicks happen when users rapid-click (or tap) on a site out of frustration. Akamai discovered that &lt;strong&gt;Rage Click likelihood depends on the latency of page usability&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rage Clicks are consistent if a user's first interaction is before the page becomes interactive (before interactive or &lt;code&gt;onload&lt;/code&gt;). This may be because event handlers are hogging CPU.&lt;/li&gt;
&lt;li&gt;In 30%+ of cases, pages were interactive after &lt;code&gt;onload&lt;/code&gt; fired and in 15%, users tried interacting sometime between &lt;code&gt;onload&lt;/code&gt; and interactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6ok1usi72wmur9ssgqjy.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Rage Clicks by first interaction and visually ready. There is a clear correlation." src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6ok1usi72wmur9ssgqjy.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the optimum time to reduce Rage Clicking?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvxcakzxga0zm55znq8rt.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="What is the optimum time to reduce Rage Clicking? Pages can be visually complete. Users expect they can interact soon after (1.3x). There is the potential for a rage-click soon after." src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvxcakzxga0zm55znq8rt.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Akamai observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The majority of users who Rage Click attempt to interact with a page between 1.25 and 1.5x the visually ready time.&lt;/li&gt;
&lt;li&gt;They suggested making sure your page is interactive and loaded before 1.3x the visually ready time. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on this study, see &lt;a href="https://speakerdeck.com/bluesmoon/ux-and-performance-metrics-that-matter-a062d37f-e6c7-4b8a-8399-472ec76bb75e" rel="noopener noreferrer"&gt;UX and performance metrics that matter&lt;/a&gt; by Philip Tellis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to Interactive can have a high correlation with overall conversion rate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://www.slideshare.net/nicjansma/reliably-measuring-responsiveness" rel="noopener noreferrer"&gt;2017 study&lt;/a&gt;, Akamai and Chrome found that across three real-world sites (in retail, travel and gaming) Time to Interactive had a high correlation with overall conversion rate. Conversions can be tapping a button to complete a purchase flow or any number of other types of responses to an interaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgy6g572iop59oigb86m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgy6g572iop59oigb86m8.png" alt="Time to Interactive impact on conversion rates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They discovered: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long Tasks directly delayed Time to Interactive. &lt;/li&gt;
&lt;li&gt;As first-page Long Task time increased, overall conversion rates decreased.&lt;/li&gt;
&lt;li&gt;Mobile devices could see a 12x Long Task time vs Desktop. &lt;/li&gt;
&lt;li&gt;Older devices could be spending half of their load-time on Long Tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: This is an obviously small sample size and every site can be different.&lt;/p&gt;

&lt;h2&gt;
  
  
  The phases of loading in more detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it happening? Is it useful?
&lt;/h3&gt;

&lt;p&gt;When the user gets timely feedback that "It is happening" they feel much better, and they perceive the site as faster. At the same time, you don't want a user to render a page which is "useful" but which they cannot interact with because it isn't yet ready. This would leave them feeling it's not &lt;a href="https://www.slideshare.net/fwdays/improve-your-web-application-using-progressive-web-metrics" rel="noopener noreferrer"&gt;usable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9vhm0ypfgg1g5qobo7x5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9vhm0ypfgg1g5qobo7x5.jpg" alt="Perceptual load timings. The timestamp of a shipped frame that contains any of these - first paint of text, first paint of SVG"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  First Paint
&lt;/h4&gt;

&lt;p&gt;First Paint marks when the browser can render anything visually different from what was displayed before navigation. It confirms that rendering has started. This can be an important metric, as the duration of ‘blank screen’ is probably the most significant indicator of page abandonment.&lt;/p&gt;

&lt;h4&gt;
  
  
  First Contentful Paint
&lt;/h4&gt;

&lt;p&gt;First Contentful Paint is when the browser renders the first content from the DOM - this could be article text, an image or SVG. The hope is this paint communicates a navigation successfully began.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it usable?
&lt;/h3&gt;

&lt;p&gt;As I've covered in &lt;a href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4" rel="noopener noreferrer"&gt;The Cost Of JavaScript&lt;/a&gt;, the web has a problem. Many sites are optimizing for content visibility but ignoring interactivity as their JavaScript for accomplishing this takes time to be processed. This means large numbers of very popular sites have &lt;a href="https://httparchive.org/reports/loading-speed#ttci" rel="noopener noreferrer"&gt;multi-second&lt;/a&gt; delays between having painted something useful and being "usable" or interactive. During this time, the web feels slow and unresponsive. &lt;/p&gt;

&lt;h4&gt;
  
  
  Time to Interactive and First Input Delay
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1h5f9t29ggc0mh5v2vqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1h5f9t29ggc0mh5v2vqy.png" alt="First Contentful Paint - FCP - when the main thread is idle, styles are loaded and browser can paint content. First Interactive - when the browser can respond to the first user input"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/WICG/time-to-interactive" rel="noopener noreferrer"&gt;Time to Interactive&lt;/a&gt; (TTI) is a metric that measures how long it takes for a web page to become interactive. This is defined as a point when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page has displayed useful content&lt;/li&gt;
&lt;li&gt;Event handlers are registered for most visible page elements&lt;/li&gt;
&lt;li&gt;When a user interacts with the page, the page consistently responds within 50ms - the user does not experience jank.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a page has a great TTI, a user can tap around the interface with high confidence it will respond to input. This strictly meets the Idle guideline of the &lt;a href="https://developers.google.com/web/fundamentals/performance/rail" rel="noopener noreferrer"&gt;RAIL performance model&lt;/a&gt;: the page yields control back to the main thread at least once every 50ms. The network is idle. Specifically, there are only two open network requests remaining.&lt;/p&gt;

&lt;p&gt;First Input Delay (FID) is TTI's complimentary metric in the field - it measures the time from a user first interacting with a page (e.g. tapping a button) to when the browser can actually respond to the interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing user-centric metrics
&lt;/h2&gt;

&lt;p&gt;A focus on optimizing user-centric metrics will ultimately improve the user experience. If you would like to reduce... &lt;/p&gt;

&lt;p&gt;Time to Interactive or First Input Delay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do less work&lt;/li&gt;
&lt;li&gt;Split up large JavaScript bundles using &lt;a href="https://web.dev/fast/reduce-javascript-payloads-with-code-splitting" rel="noopener noreferrer"&gt;code-splitting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Split up long tasks. Consider moving intensive off-main thread to workers.&lt;/li&gt;
&lt;li&gt;Postpone non-critical work until after page load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First Paint and First Contentful Paint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove render blocking scripts from head&lt;/li&gt;
&lt;li&gt;Identify the &lt;a href="https://www.smashingmagazine.com/2015/08/understanding-critical-css/" rel="noopener noreferrer"&gt;critical CSS&lt;/a&gt; needed and inline in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;App Shell pattern - improve user perception rendering UI skeleton &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Monitoring metrics
&lt;/h2&gt;

&lt;p&gt;Performance tools like &lt;a href="https://developers.google.com/speed/pagespeed/insights/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt;, &lt;a href="https://webpagetest.org/easy" rel="noopener noreferrer"&gt;WPT&lt;/a&gt; and &lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; capture user-centric loading metrics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmt5uxwewjkgy3bntc8av.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmt5uxwewjkgy3bntc8av.png" alt="First Contentful Paint, Speed Index, Time to Interactive, First Meaningful Paint, First CPU Idle in Lighthouse"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For CLI users, they are also available via &lt;a href="https://github.com/paulirish/pwmetrics" rel="noopener noreferrer"&gt;pwmetrics&lt;/a&gt; by Paul Irish and Artem Denysov. &lt;/p&gt;

&lt;p&gt;For monitoring metrics in the field (via RUM), I recommend the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformancePaintTiming" rel="noopener noreferrer"&gt;Paint Timing API&lt;/a&gt; which provides First Paint and First Contentful Paint. A polyfill for &lt;a href="https://github.com/GoogleChromeLabs/first-input-delay" rel="noopener noreferrer"&gt;First Input Delay&lt;/a&gt; is also available. Paired with an analytics service these enable logging progressive web metrics for your real users.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developers.google.com/web/tools/chrome-user-experience-report/" rel="noopener noreferrer"&gt;Chrome User Experience Report&lt;/a&gt; (&lt;a href="https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fchrome.com" rel="noopener noreferrer"&gt;also in PageSpeed Insights&lt;/a&gt;) provides access to some of these metrics (like First Contentful Paint and First Input Delay) for your real users using Chrome:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2zyvauug3fdk6kqudgi6.png" class="article-body-image-wrapper"&gt;&lt;img alt="Chrome User Experience Report distributions for metrics" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2zyvauug3fdk6kqudgi6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also like &lt;a href="https://speedcurve" rel="noopener noreferrer"&gt;SpeedCurve&lt;/a&gt; or &lt;a href="https://calibreapp.com" rel="noopener noreferrer"&gt;Calibre&lt;/a&gt; for tracking metrics like FCP and TTI over time. It allows me to set performance budgets for them too which can help detect regressions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4mw8beesk2j3lc3qna4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4mw8beesk2j3lc3qna4e.png" alt="Speedcurve metrics tracking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging metrics
&lt;/h2&gt;

&lt;p&gt;Chrome DevTools now highlights performance metrics in the Performance panel. These can be found under Timings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhb2e04oc9ej4h8b4gqhn.png" class="article-body-image-wrapper"&gt;&lt;img alt="DevTools Metrics in performance panel" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhb2e04oc9ej4h8b4gqhn.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to other surfaces these metrics are exposed, this can provide value while attempting to improve times in your iteration workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=XvZ7-Uh0R4Q" rel="noopener noreferrer"&gt;The Latest in Metrics &amp;amp; Measurement - Paul Irish&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/bluesmoon/ux-and-performance-metrics-that-matter-a062d37f-e6c7-4b8a-8399-472ec76bb75e" rel="noopener noreferrer"&gt;UX and performance metrics that matter - Philip Tellis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics" rel="noopener noreferrer"&gt;User-centric Performance Metrics - Philip Walton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.slideshare.net/nicjansma/reliably-measuring-responsiveness" rel="noopener noreferrer"&gt;Reliably Measuring Responsiveness In The Wild - Shubhie Panicker and Nick Jansma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ymxs8OSXiUA" rel="noopener noreferrer"&gt;Speed Tooling - State Of the Union: Elizabeth Sweeny and Paul Irish&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://calibreapp.com/blog/time-to-interactive/" rel="noopener noreferrer"&gt;Focusing on the human-centric metrics - Radimir Bitsov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.slideshare.net/fwdays/improve-your-web-application-using-progressive-web-metrics" rel="noopener noreferrer"&gt;Improve your web application using Progressive Web Metrics - Artem Denysov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philipwalton.com/articles/why-web-developers-need-to-care-about-interactivity/" rel="noopener noreferrer"&gt;Why Web Developers Need to Care about Interactivity - Philip Walton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/updates/2018/05/first-input-delay" rel="noopener noreferrer"&gt;First Input Delay - Philip Walton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ULU-4-ApcjM" rel="noopener noreferrer"&gt;First Input Delay - State Of The Web - Rick Viscomi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstory.com/resources/guide-to-understanding-frustrating-user-experiences-online/" rel="noopener noreferrer"&gt;The Guide to Understanding Frustration Online&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/document/d/1g0J5JIcICFyXYM6ifBF6vNd_qUPIfpf6YAfFHE1TYRE/edit#heading=h.c79uz11ezek4" rel="noopener noreferrer"&gt;First Input Delay: Correlation with TTI - Tim Dresser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wicg.github.io/event-timing/" rel="noopener noreferrer"&gt;An Event Timing API - Tim Dresser and Nicolás Peña Moreno&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webperf</category>
      <category>lighthouse</category>
      <category>webdev</category>
      <category>timetointeractive</category>
    </item>
    <item>
      <title>Speed up next-page navigations with prefetching</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Thu, 13 Dec 2018 01:40:06 +0000</pubDate>
      <link>https://forem.com/addyosmani/speed-up-next-page-navigations-with-prefetching-4285</link>
      <guid>https://forem.com/addyosmani/speed-up-next-page-navigations-with-prefetching-4285</guid>
      <description>&lt;h2&gt;
  
  
  What is prefetching?
&lt;/h2&gt;

&lt;p&gt;Loading web pages &lt;em&gt;could&lt;/em&gt; be made faster by fetching the next page (or set of resources for the next page) a user is likely to visit ahead of time. We call this prefetching. This can make subsequent navigations appear to load instantly in some cases. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwns51uqa5u5z12zfn9zk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwns51uqa5u5z12zfn9zk.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modern prefetching can be achieved in a number of ways. Most commonly this is done using the browser’s &lt;a href="https://w3c.github.io/resource-hints/#prefetch" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt;&lt;/a&gt; &lt;a href="https://w3c.github.io/resource-hints/" rel="noopener noreferrer"&gt;Resource Hint&lt;/a&gt;, which works declaratively via HTML..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- HTML --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"prefetch"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/pages/next-page.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"prefetch"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/js/chat-widget.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or a HTTP Header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Link: &amp;lt;/js/chat-widget.js&amp;gt;; rel=prefetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefetching is supported in Webpack too (see &lt;a href="https://medium.com/webpack/link-rel-prefetch-preload-in-webpack-51a52358f84c" rel="noopener noreferrer"&gt;prefetch/preload in Webpack&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;Without&lt;/span&gt; &lt;span class="nx"&gt;prefetching&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ChatWidget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;With&lt;/span&gt; &lt;span class="nx"&gt;prefetching&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* webpackPrefetch: true */&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ChatWidget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Webpack's runtime will inject the correct prefetch statements in the page once the parent chunk has finished loading. In the above case, &lt;code&gt;&amp;lt;link rel="prefetch" href="chat-widget.js"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Per &lt;a href="http://caniuse.com/prefetch" rel="noopener noreferrer"&gt;CanIUse&lt;/a&gt;, &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; has decent cross-browser support.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhrxxjcn5xrs06d6jobo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhrxxjcn5xrs06d6jobo.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a resource in Chrome is prefetched with &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt;, it will be fetched at a low priority and &lt;a href="https://chromium-review.googlesource.com/c/chromium/src/+/1343101" rel="noopener noreferrer"&gt;kept&lt;/a&gt; around for 5 minutes. This lasts until the resource has been consumed, at which point the normal cache-control rules for the resource will apply. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6tqkyeoydmzlwh0p6dv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6tqkyeoydmzlwh0p6dv.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unsure what pages on your site may be worth considering prefetching? Check out our experimental &lt;a href="https://predictjs.firebaseapp.com/" rel="noopener noreferrer"&gt;helper tool&lt;/a&gt; for suggested "top next pages" (based on your analytics):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl51ew3uifosd8n3265lm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl51ew3uifosd8n3265lm.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Prefetching" can also mostly be accomplished using &lt;a href="https://developers.google.com/web/fundamentals/primers/service-workers/" rel="noopener noreferrer"&gt;Service Workers&lt;/a&gt; [see &lt;a href="https://googlechrome.github.io/samples/service-worker/prefetch/" rel="noopener noreferrer"&gt;prefetching during registration&lt;/a&gt;]/fetch() or XHR. Note however, not all of these mechanisms have the same "low priority" fetch behavior as &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is using prefetching today?
&lt;/h2&gt;

&lt;p&gt;Several brands you've probably heard of use prefetching in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://medium.com/dev-channel/a-netflix-web-performance-case-study-c0bcde26a9d9" rel="noopener noreferrer"&gt;Netflix&lt;/a&gt; prefetch JavaScript bundles needed for subsequent navigations ahead of time&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.indiegogo.com/" rel="noopener noreferrer"&gt;IndieGogo&lt;/a&gt; prefetch Stripe's JavaScript library for credit-card processing on future pages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://london.craigslist.org/" rel="noopener noreferrer"&gt;Craigslist&lt;/a&gt; prefetch their JS bundle for search results pages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.heineken.com/us/agegateway?returnurl=%2f" rel="noopener noreferrer"&gt;Heineken&lt;/a&gt; prefetch JS and CSS bundles pages after the date-of-birth verification page may need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the challenges of prefetching?
&lt;/h2&gt;

&lt;p&gt;When a user is on a constrained network connection or a limited data-plan, every last byte counts. While prefetching a number of pages ahead of time can make sense for users on a great WiFi connection, it requires care for those that are not. You don't want to waste anyone's data plan.&lt;/p&gt;

&lt;p&gt;You can use the &lt;a href="https://dev.to/addyosmani/adaptive-serving-using-javascript-and-the-network-information-api-331p"&gt;navigator.connection.effectiveType&lt;/a&gt; API (part of NetInfo) to only prefetch pages when a user is on a connection that is effectively 4G. &lt;/p&gt;

&lt;p&gt;One thing to also be careful about with prefetch is that if requests aren't finished by the time a navigation is initiated, the in-flight prefetch request is cancelled. This can mean wasted bytes so just be sure a user is likely to actually see value from these fetched when using this capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case study
&lt;/h2&gt;

&lt;p&gt;When Japanese publisher &lt;a href="https://r.nikkei.com/" rel="noopener noreferrer"&gt;Nikkei&lt;/a&gt; were confident users would navigate to specific pages, they didn't wait for a navigation to happen. &lt;/p&gt;

&lt;p&gt;They leveraged &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; to prefetch the next page before users tapped on links. Below we can see the impact of waiting for network + server overhead to deliver a page (taking a total of ~880ms). Compare this to prefetching, with almost no request overhead and an immediate page navigation (~37ms). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxh6mvgcf1msmc5swk02.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxh6mvgcf1msmc5swk02.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More on Nikkei can be found in this recent &lt;a href="https://developers.google.com/web/showcase/2018/nikkei" rel="noopener noreferrer"&gt;case study&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is predictive prefetching?
&lt;/h2&gt;

&lt;p&gt;Sites using prefetching typically have a good idea of what top-level pages or resources a user is going to visit and can manually add &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; tags for them. This manual process can however become harder to achieve for large sites at scale. Once you go several levels deep, it can be difficult to know what the "best" next page is going to be nor how to wire this up without it being a manual process.&lt;/p&gt;

&lt;p&gt;Predictive prefetching uses additional data (e.g analytics) to predict what documents users are likely to visit given any URL on a site. This data can then be used to &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; those pages, improving subsequent page load times. Predictions can be optionally improved using machine learning to ensure only pages with the highest changes of being accessed are fetched ahead of time. &lt;/p&gt;

&lt;p&gt;Although there’s nothing here specific to it, Google Analytics has a &lt;a href="https://developers.google.com/analytics/devguides/reporting/core/v4/" rel="noopener noreferrer"&gt;Reporting API&lt;/a&gt; with just enough information to build out predictive prefetching for sites. Minko Gechev, Katie Hempenius and I took advantage of this recently to build a project called &lt;a href="https://guess-js.github.io/" rel="noopener noreferrer"&gt;Guess.js&lt;/a&gt;, which has solutions for 1) predictively prefetching relevant pages and 2) predictively bundling and prefetching the right JavaScript resources that may be needed for those pages (via webpack).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can I learn more about predictive prefetching?
&lt;/h2&gt;

&lt;p&gt;Rick Viscomi chatted with Katie Hempenius and I about this topic in depth in case interested in learning more:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/03qta1Ufn3c"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We're going to continue exploring opportunities to evolve prefetching on the web. Most recently, we've explored privacy-preserving preserving prefetching using &lt;a href="https://developers.google.com/web/updates/2018/11/signed-exchanges" rel="noopener noreferrer"&gt;Signed Exchanges&lt;/a&gt;, a subset of the emerging &lt;a href="https://github.com/WICG/webpackage" rel="noopener noreferrer"&gt;web packaging&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;PS: Keep an eye out for &lt;a href="https://github.com/GoogleChromeLabs/quicklink" rel="noopener noreferrer"&gt;quicklink&lt;/a&gt; - a &amp;lt;1KB library I'm releasing that aims to enable faster next-page navigations by prefetching in-viewport links during idle time :)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With thanks to Yoav Weiss for his helpful input on how &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; works behind the scenes in Chrome&lt;/em&gt;&lt;/p&gt;

</description>
      <category>prefetch</category>
      <category>webperf</category>
      <category>optimize</category>
      <category>performance</category>
    </item>
    <item>
      <title>The State Of JavaScript</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Sat, 27 Oct 2018 02:44:54 +0000</pubDate>
      <link>https://forem.com/addyosmani/the-state-of-javascript-9f3</link>
      <guid>https://forem.com/addyosmani/the-state-of-javascript-9f3</guid>
      <description>&lt;p&gt;I recently talked about the &lt;a href="https://youtu.be/i5R7giitymk" rel="noopener noreferrer"&gt;State Of JavaScript&lt;/a&gt; on the State Of The Web show. Yes, I'm a one-trick pony... 😃 &lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/i5R7giitymk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;On the client, as you pay for things you can't always easily see it becomes important to understand how &lt;a href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4" rel="noopener noreferrer"&gt;the cost of JavaScript&lt;/a&gt; might impact your site.&lt;/p&gt;

&lt;p&gt;How do I tackle these problems?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/addyosmani/shine-a-light-on-javascript-performance-with-lighthouse-1opf"&gt;Start off with Lighthouse&lt;/a&gt; 💡🏠&lt;/li&gt;
&lt;li&gt;Check how well your site performs on some real phones. There are a number of these setup on &lt;a href="https://webpagetest.org" rel="noopener noreferrer"&gt;WebPageTest&lt;/a&gt;. If your team can afford them, a local device lab is always useful too 📱&lt;/li&gt;
&lt;li&gt;If JavaScript is impacting your metrics, it's probably time to analyze why. I regularly use &lt;a href="https://www.npmjs.com/package/source-map-explorer" rel="noopener noreferrer"&gt;source-map-explorer&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;webpack-bundle-analyzer&lt;/a&gt; and &lt;a href="https://bundlephobia.com" rel="noopener noreferrer"&gt;bundlephobia&lt;/a&gt; to discover opportunities to trim down my bundles. &lt;/li&gt;
&lt;li&gt;I use the DevTools code-coverage highlights bundle weight-loss opportunities.&lt;/li&gt;
&lt;li&gt;Try to leverage code-splitting patterns. Got a large vendor bundle? Maybe only a few of those dependencies are needed for the initial page load. Large app bundle? Is all that logic really needed right away? Libraries like React are starting to get better at supporting code-splitting out of the box (see &lt;a href="https://reactjs.org/blog/2018/10/23/react-v-16-6.html#reactlazy-code-splitting-with-suspense" rel="noopener noreferrer"&gt;React.lazy()&lt;/a&gt;). &lt;/li&gt;
&lt;li&gt;Consider using &lt;a href="https://babeljs.io/docs/en/babel-preset-env" rel="noopener noreferrer"&gt;babel-preset-env&lt;/a&gt; to generate bundles for modern vs legacy browsers. The &lt;a href="https://philipwalton.com/articles/deploying-es2015-code-in-production-today/" rel="noopener noreferrer"&gt;module/nomodule pattern&lt;/a&gt; allows you to serve these in a backwards compatible way. &lt;/li&gt;
&lt;li&gt;Leverage compression techniques. I've been suggesting teams try out &lt;a href="https://css-tricks.com/brotli-static-compression/" rel="noopener noreferrer"&gt;Brotli&lt;/a&gt; for static asset compression. In my own apps, I've seen a 20-30% decrease in bundle size over the wire for JS. If you're a Node shop, &lt;a href="https://github.com/aickin/shrink-ray" rel="noopener noreferrer"&gt;shrink-ray&lt;/a&gt; or &lt;a href="https://www.npmjs.com/package/shrink-ray-current" rel="noopener noreferrer"&gt;shrink-ray-current&lt;/a&gt; are solid middlewares for serving Brotli that I've found work reliably.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>optimization</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shine a light on JavaScript performance with Lighthouse</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Thu, 18 Oct 2018 18:41:20 +0000</pubDate>
      <link>https://forem.com/addyosmani/shine-a-light-on-javascript-performance-with-lighthouse-1opf</link>
      <guid>https://forem.com/addyosmani/shine-a-light-on-javascript-performance-with-lighthouse-1opf</guid>
      <description>&lt;p&gt;Unsure if the &lt;a href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4" rel="noopener noreferrer"&gt;cost of JavaScript&lt;/a&gt; is too high for your user-experience? 🙃 &lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; has a &lt;a href="https://developers.google.com/web/tools/lighthouse/audits/bootup" rel="noopener noreferrer"&gt;JavaScript execution time audit&lt;/a&gt; that measures the total impact of JavaScript on your page's load performance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3a0l5jnt1sqhtfa3rbl7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3a0l5jnt1sqhtfa3rbl7.png" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it. It's in the Chrome DevTools &lt;a href="https://developers.google.com/web/updates/2017/05/devtools-release-notes#lighthouse" rel="noopener noreferrer"&gt;Audits&lt;/a&gt; panel today. It's also available via &lt;a href="https://webpagetest.org/easy" rel="noopener noreferrer"&gt;WebPageTest&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;For the above content site, it takes 51s (oi vey) for the browser to process just the primary bundle for this site on mobile. Including network transfer time, a user could be waiting for up to a minute to &lt;a href="https://philipwalton.com/articles/why-web-developers-need-to-care-about-interactivity/" rel="noopener noreferrer"&gt;interact&lt;/a&gt; with this page ⏳😪&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl8zj3v81jb6e4c3baqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl8zj3v81jb6e4c3baqk.png" width="800" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's time spent parsing, compiling and executing script on a median mobile device configuration. &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt; (offering a similar content experience) is able to load their main bundle with a minimal dependency on script execution ❤️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fam8bnzcfnfx6gkbl95iv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fam8bnzcfnfx6gkbl95iv.png" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How can we improve the cost of JS in the original site? &lt;/p&gt;

&lt;p&gt;By only shipping JavaScript the user really needs upfront. We can lazily load the rest as needed using techniques like &lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-javascript/code-splitting/" rel="noopener noreferrer"&gt;code-splitting&lt;/a&gt;. I use the DevTools &lt;a href="https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage" rel="noopener noreferrer"&gt;Code Coverage&lt;/a&gt; feature to help here. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiodz8vcte9qvm4knch9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiodz8vcte9qvm4knch9v.png" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I hit record and load up the above experience then interact for a while, we can see about 57% of the code loaded upfront may not be needed. That's a great candidate for something that can be loaded on-demand.&lt;/p&gt;

&lt;p&gt;If you haven't checked out Lighthouse before, it's full of useful nuggets like checks for whether you're correctly minifying your scripts or compressing them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6bapee7get96rt9w01k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6bapee7get96rt9w01k.png" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you're into automation using headless Chrome, there's also a useful &lt;a href="https://github.com/GoogleChromeLabs/puppeteer-examples#code_coveragejs" rel="noopener noreferrer"&gt;code-coverage example&lt;/a&gt; for &lt;a href="https://developers.google.com/web/tools/puppeteer/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; that can visualize JS code coverage usage across page loads. &lt;/p&gt;

&lt;p&gt;Wrapping up.. 🎁&lt;/p&gt;

&lt;p&gt;JavaScript can have a large impact on your user-experience; Lighthouse can highlight opportunities to improve here. To keep JavaScript transmission and processing times low:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only send the code that your users need.&lt;/li&gt;
&lt;li&gt;Minify and compress your scripts.&lt;/li&gt;
&lt;li&gt;Remove unused code and dependencies.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>lighthouse</category>
      <category>bundle</category>
    </item>
    <item>
      <title>Adaptive Serving using JavaScript and the Network Information API</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Mon, 08 Oct 2018 15:46:46 +0000</pubDate>
      <link>https://forem.com/addyosmani/adaptive-serving-using-javascript-and-the-network-information-api-331p</link>
      <guid>https://forem.com/addyosmani/adaptive-serving-using-javascript-and-the-network-information-api-331p</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;code&gt;navigator.connection.effectiveType&lt;/code&gt; is useful for delivering different assets based on the quality of the user's network connection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2ymyle56383fvsm3t1t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2ymyle56383fvsm3t1t.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType" rel="noopener noreferrer"&gt;effectiveType&lt;/a&gt; is a property of the &lt;a href="http://w3c.github.io/netinfo/" rel="noopener noreferrer"&gt;Network Information API&lt;/a&gt;, exposed to JavaScript via the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/connection" rel="noopener noreferrer"&gt;navigator.connection&lt;/a&gt; object. In Chrome, you can drop the following into DevTools to see your effective connection type (ECT):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effectiveType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible values for &lt;code&gt;effectiveType&lt;/code&gt; are 'slow-2g', '2g', '3g', or '4g'. On slow connections this capability allows you to improve how quickly pages load by serving lower-quality versions of resources. &lt;/p&gt;

&lt;p&gt;Before Chrome 62, we only exposed the theoretical network connection type to developers (via &lt;code&gt;navigator.connection.type&lt;/code&gt;) rather than the network quality actually experienced by the client. &lt;/p&gt;

&lt;p&gt;Chrome's implementation of effective connection type is now determined using a combination of recently observed round-trip times (rtt) and downlink values. &lt;/p&gt;

&lt;p&gt;It summarizes measured network performance as the cellular connection type (e.g. 2G) most similar, even if the actual connection is WiFi. i.e. picture you're on Starbucks WiFi, but your actual effective network type is 2G or 3G.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8bg78m8atw64ermw5r5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8bg78m8atw64ermw5r5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What about responding to changes in network quality? We can use the &lt;code&gt;connection.onchange&lt;/code&gt; event listener to monitor for connection changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onConnectionChange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rtt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;downlink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;effectiveType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;saveData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Effective network connection type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;effectiveType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Downlink Speed/bandwidth estimate: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;downlink&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Mb/s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Round-trip time estimate: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rtt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Data-saver mode on/requested: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onConnectionChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a quick test where I emulated a "Low-end mobile" profile in DevTools and was able to switch from "4g" to "2g" conditions: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k2k5ke8p1wklrlz8jhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k2k5ke8p1wklrlz8jhc.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;effectiveType&lt;/code&gt; is supported in Chrome, Opera and Firefox on Android. A number of other network quality hints are available on &lt;code&gt;navigator.connection&lt;/code&gt;, including &lt;code&gt;rtt&lt;/code&gt;, &lt;code&gt;downlink&lt;/code&gt; and &lt;code&gt;downlinkMax&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An open-source project I've used &lt;code&gt;effectiveType&lt;/code&gt; in was a Vue.js &lt;a href="https://oodle-demo.firebaseapp.com" rel="noopener noreferrer"&gt;Google Doodles&lt;/a&gt; app. Using data-binding, we were able to set a &lt;code&gt;connection&lt;/code&gt; property to either &lt;code&gt;fast&lt;/code&gt; or &lt;code&gt;slow&lt;/code&gt; based on ECT values. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;low-2g|2g|3g/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effectiveType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allowed us to conditionally render different output (a video vs. a low-res image) depending on the user's effective connection type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;   &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"connection === 'fast'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="c"&gt;&amp;lt;!-- 1.3MB video --&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"theatre"&lt;/span&gt; &lt;span class="na"&gt;autoplay&lt;/span&gt; &lt;span class="na"&gt;muted&lt;/span&gt; &lt;span class="na"&gt;playsinline&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/static/img/doodle-theatre.webm"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/webm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/static/img/doodle-theatre.mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- 28KB image --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"connection === 'slow'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"theatre"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/static/img/doodle-theatre-poster.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd44b99mn5fndd6j3xar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd44b99mn5fndd6j3xar.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Max Böck wrote an interesting article about &lt;a href="https://mxb.at/blog/connection-aware-components/" rel="noopener noreferrer"&gt;network-aware components&lt;/a&gt; using React. He similarly highlighted how to render different components based on the network speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connectionType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Video&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;videoSrc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;imageSrc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hires&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;imageSrc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lowres&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: You can pair &lt;code&gt;effectiveType&lt;/code&gt; with Service Workers to adapt to when users are offline in addition to slower effective connection types.&lt;/p&gt;

&lt;p&gt;For debugging, you can override the network quality estimate using the Chrome flag "force-effective-connection-type" which can be set from chrome://flags. DevTools Network emulation can provide a limited debugging experience for ECT too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;effectiveType&lt;/code&gt; values are also exposed via &lt;a href="https://www.chromestatus.com/features/5407907378102272" rel="noopener noreferrer"&gt;Client Hints&lt;/a&gt; allowing developers to convey Chrome's network connection speed to servers.&lt;/p&gt;

&lt;p&gt;Further reading on this feature, see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://googlechrome.github.io/samples/network-information/" rel="noopener noreferrer"&gt;Google Chrome Network Information API Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mxb.at/blog/connection-aware-components/" rel="noopener noreferrer"&gt;Connection-aware components&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://deanhume.com/dynamic-resources-using-the-network-information-api-and-service-workers/" rel="noopener noreferrer"&gt;Dynamic resources using the NetInfo API and service workers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justmarkup.com/log/2017/11/network-based-image-loading/" rel="noopener noreferrer"&gt;Network-based image loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.chromium.org/2017/09/chrome-62-beta-network-quality.html" rel="noopener noreferrer"&gt;Chrome 62 Beta: Network Quality Estimator API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/UVfNMH50aaQ" rel="noopener noreferrer"&gt;Intent to Ship: NetInfo API extension for network quality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also find this post on &lt;a href="https://addyosmani.com/blog/adaptive-serving/" rel="noopener noreferrer"&gt;addyosmani.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
      <category>adaptiveserving</category>
    </item>
    <item>
      <title>I'm Addy Osmani, Ask Me Anything!</title>
      <dc:creator>Addy Osmani</dc:creator>
      <pubDate>Wed, 11 Jul 2018 16:43:21 +0000</pubDate>
      <link>https://forem.com/addyosmani/im-addy-osmani-ask-me-anything-596c</link>
      <guid>https://forem.com/addyosmani/im-addy-osmani-ask-me-anything-596c</guid>
      <description>&lt;p&gt;My name is Addy and I’m an engineering lead on the &lt;a href="https://www.google.com/chrome/" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; team at Google leading Developer Experience. We do this through Chrome developer tools, web standards, working with sites &amp;amp; frameworks in the ecosystem and through tools like &lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; and &lt;a href="https://developers.google.com/web/tools/workbox/" rel="noopener noreferrer"&gt;Workbox&lt;/a&gt;. I give talks about speed and have written books like Learning JavaScript Design Patterns and Essential Image Optimization. I am excited about Developer Experience and how &lt;a href="https://addyosmani.com/blog/personal-software/" rel="noopener noreferrer"&gt;personal software&lt;/a&gt; via AI might democratize software development.&lt;/p&gt;

&lt;p&gt;To learn more, you can find me on Twitter at &lt;a href="https://twitter.com/addyosmani" rel="noopener noreferrer"&gt;@addyosmani&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ama</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
