<?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>DEV Community: Ryan VerWey</title>
    <description>The latest articles on DEV Community by Ryan VerWey (@rverwey).</description>
    <link>https://dev.to/rverwey</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1213395%2Fb0a67ef6-3d3c-41a1-b327-06e902153dc4.png</url>
      <title>DEV Community: Ryan VerWey</title>
      <link>https://dev.to/rverwey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rverwey"/>
    <language>en</language>
    <item>
      <title>The Effort to Reach: Designing for Fitts’s Law</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:32:43 +0000</pubDate>
      <link>https://dev.to/rverwey/the-effort-to-reach-designing-for-fittss-law-576</link>
      <guid>https://dev.to/rverwey/the-effort-to-reach-designing-for-fittss-law-576</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-effort-to-reach-fitts-law-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fitts’s Law describes how movement time depends on the distance to a target and the target’s width. In comparable pointing conditions, a larger target or a shorter journey generally makes acquisition easier. For interface design, that means giving actions a usable hit area, separating competing controls, and placing the next step where the task makes it useful.&lt;/p&gt;

&lt;p&gt;The interesting work starts when those goals compete.&lt;/p&gt;

&lt;p&gt;An interface can have generously sized buttons and still make someone travel across the screen after every edit. It can put actions close together and make the wrong one dangerously easy to hit. It can enlarge an invisible click area without giving anyone confidence that the area is available.&lt;/p&gt;

&lt;p&gt;This seventh installment of &lt;strong&gt;Applied Design Theory&lt;/strong&gt; examines those tradeoffs. My central argument is that &lt;strong&gt;a target should be easy to acquire in the context of an intentional action&lt;/strong&gt;. Size matters, but so do the path, the surrounding controls, and what happens after a mistake.&lt;/p&gt;

&lt;p&gt;The previous article, &lt;a href="https://www.ryanverwey.dev/blog/the-cost-of-a-pause-doherty-threshold-ui" rel="noopener noreferrer"&gt;The Cost of a Pause&lt;/a&gt;, looked at what happens after someone acts. Here, we move one step earlier: the effort required to make that action land.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Fitts’s Law Actually Models
&lt;/h2&gt;

&lt;p&gt;Paul Fitts’s 1954 paper investigated the relationship between movement and precision using controlled motor tasks, including repeated tapping between targets. The work connected the demands of movement with ideas from information theory. It preceded modern graphical interfaces; it was not a study of mobile checkout buttons. MacKenzie’s &lt;a href="https://www.yorku.ca/mack/hci1992.html" rel="noopener noreferrer"&gt;1992 research review&lt;/a&gt; explains the original experiments and their later application to computer input.&lt;/p&gt;

&lt;p&gt;The practical distinction is between reaching a known target and completing an entire task. Finding a control, interpreting its label, deciding whether to proceed, moving to it, activating it, and waiting for the result are different parts of an interaction. Fitts’s Law is especially useful for examining the movement component. It does not turn every delay into a target-size problem.&lt;/p&gt;

&lt;p&gt;Consider a hypothetical account page with an unfamiliar icon labeled only by a tooltip. Increasing the button’s hit area might make it easier to select after someone identifies it. That change would not explain what the icon means. A clear label addresses a different obstacle.&lt;/p&gt;

&lt;p&gt;That distinction connects to &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;The Decision Tax&lt;/a&gt;: a well-placed target cannot compensate for an unclear choice. The user needs both a decision they understand and a control they can operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the Equation Without Inventing Precision
&lt;/h2&gt;

&lt;p&gt;A common version used in human-computer interaction is the Shannon formulation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MT = a + b × log₂(1 + D / W)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;MT&lt;/strong&gt; is predicted movement time, &lt;strong&gt;D&lt;/strong&gt; is movement distance, and &lt;strong&gt;W&lt;/strong&gt; represents target width for the modeled task. The coefficients &lt;strong&gt;a&lt;/strong&gt; and &lt;strong&gt;b&lt;/strong&gt; are fitted from observations. They are not universal constants that can be copied into every product. MacKenzie’s &lt;a href="https://www.yorku.ca/mack/JMB89.html" rel="noopener noreferrer"&gt;1989 paper&lt;/a&gt; develops this formulation; the added 1 distinguishes it from the familiar original logarithmic form.&lt;/p&gt;

&lt;p&gt;The logarithmic term is the &lt;em&gt;index of difficulty&lt;/em&gt;. We can use it to compare hypothetical geometry without pretending to know how many milliseconds a particular user will need.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Illustrative condition&lt;/th&gt;
&lt;th&gt;Distance D&lt;/th&gt;
&lt;th&gt;Width W&lt;/th&gt;
&lt;th&gt;log₂(1 + D / W)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;3.46 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Double the width&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;2.58 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Halve the distance&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;2.58 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do both&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;1.81 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are calculated examples in consistent arbitrary distance units, not measured user results. They show why size and placement deserve attention together. In this simplified comparison, doubling width and halving distance produce the same ratio. Neither calculation establishes a percentage improvement in task completion or conversion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx68mzjyzdi044bq6gcoe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx68mzjyzdi044bq6gcoe.webp" alt="Conceptual comparison of wider targets at similar travel distances and shorter travel to equally sized targets." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Two variables to inspect: the room available for landing and the distance to reach it. The illustration is schematic, not a scale drawing or measured performance chart.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Width Is More Specific Than Overall Area
&lt;/h3&gt;

&lt;p&gt;A long, shallow button can occupy plenty of pixels while still demanding precise vertical positioning. In the basic one-dimensional model, width is measured along the movement axis. Real interfaces introduce two-dimensional shapes and different approach directions.&lt;/p&gt;

&lt;p&gt;MacKenzie and Buxton’s &lt;a href="https://www.yorku.ca/mack/CHI92.html" rel="noopener noreferrer"&gt;1992 experiment&lt;/a&gt; compared alternative ways of representing target width for rectangular targets. Their results support taking both dimensions seriously instead of automatically treating a rectangle’s horizontal length as its usable width.&lt;/p&gt;

&lt;p&gt;For a design review, I would inspect the shortest dimension, the likely approach, and the entire interactive boundary. A wide text link with very little height should not receive a free pass because it looks substantial in a screenshot. Nor would I use the experiment’s fitted coefficients as a performance promise for a different device or audience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the Hit Area, Then Make It Understandable
&lt;/h2&gt;

&lt;p&gt;The visible symbol and the interactive region are separate design decisions.&lt;/p&gt;

&lt;p&gt;Imagine an overflow menu represented by three dots in a table row. The dots might be visually small because they are a secondary action. That does not require making those few painted pixels the entire target. A real button can provide padding around the icon, an accessible name, and a visible interaction state.&lt;/p&gt;

&lt;p&gt;The next question is whether people can recognize that larger area. Budiu’s &lt;a href="https://www.nngroup.com/articles/fitts-law/" rel="noopener noreferrer"&gt;Nielsen Norman Group article&lt;/a&gt; notes that invisible padding may leave users aiming cautiously at the visible symbol. My application of that observation is to make the button’s shape or surrounding treatment communicate its operable area, particularly for important actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcujd98xqmf7m8m4wkwz5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcujd98xqmf7m8m4wkwz5.webp" alt="Two ellipsis controls contrast a small icon-only hit area with a larger full-button interactive area." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. The active region can extend beyond the icon. This conceptual comparison is not a pixel specification; verify the actual boundaries in the implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is a small implementation example. It uses an ordinary button rather than attaching behavior directly to an icon:&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;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row-action"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"More actions for Quarterly report"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;•••&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.row-action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inline-flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;min-inline-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;min-block-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#173b3b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.row-action&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nx"&gt;#005fcc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&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 48-pixel dimensions are an example design choice, not a value derived from Fitts’s equation. A production menu still needs appropriate expanded-state communication, menu behavior, and focus management. This fragment only demonstrates how the button owns its hit area. W3C’s &lt;a href="https://www.w3.org/WAI/WCAG22/Techniques/css/C44" rel="noopener noreferrer"&gt;CSS target-size technique&lt;/a&gt; similarly distinguishes a control’s size from its text or icon.&lt;/p&gt;

&lt;p&gt;I would test the padded edges, not just the center. I would also check whether a parent clips the control, a neighboring link overlaps it, or a transparent overlay intercepts the click. A component’s declared dimensions are only useful if the resulting region actually activates the intended action.&lt;/p&gt;

&lt;p&gt;For a design system, this belongs in the shared control. Fixing twenty individual icons independently creates twenty opportunities for the next screen to behave differently. This is the kind of implementation responsibility I discuss in &lt;a href="https://www.ryanverwey.dev/blog/atomic-design-system-angular" rel="noopener noreferrer"&gt;my Atomic Design work&lt;/a&gt;: reusable components should carry dependable behavior as well as consistent appearance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size and Spacing Solve Different Problems
&lt;/h2&gt;

&lt;p&gt;Size gives the intended action a larger landing region. Spacing helps keep a near miss from becoming another action. Increasing one does not automatically improve the other.&lt;/p&gt;

&lt;p&gt;In our hypothetical table, suppose Edit and Delete sit side by side. Expanding both hit areas through overlapping invisible regions would make ownership ambiguous. Increasing padding within two distinct buttons, then giving the controls separation, creates a clearer result.&lt;/p&gt;

&lt;p&gt;I would also question whether those actions belong together in that form. Frequent editing and occasional deletion may deserve different presentation. That is a task and consequence judgment, not a claim that Fitts’s Law prescribes a particular menu layout.&lt;/p&gt;

&lt;p&gt;Spacing also has a cost. Pushing every control far apart can make sequential work tedious and force more scrolling. The goal is enough separation to distinguish actions while keeping related work reasonably close. Inspect the surrounding target boundaries instead of choosing a gap token in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What WCAG Requires, and What It Does Not
&lt;/h3&gt;

&lt;p&gt;For web interfaces, two different target-size criteria are often confused:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WCAG criterion&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Size requirement before exceptions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2.5.8 Target Size (Minimum)&lt;/td&gt;
&lt;td&gt;AA&lt;/td&gt;
&lt;td&gt;At least 24 × 24 CSS pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.5.5 Target Size (Enhanced)&lt;/td&gt;
&lt;td&gt;AAA&lt;/td&gt;
&lt;td&gt;At least 44 × 44 CSS pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html" rel="noopener noreferrer"&gt;2.5.8&lt;/a&gt;, an undersized target can satisfy the spacing exception when a 24-CSS-pixel-diameter circle centered on its bounding box does not intersect another target or the corresponding circle around another undersized target. Other exceptions cover equivalent controls, inline targets, unmodified user-agent controls, and essential presentation.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html" rel="noopener noreferrer"&gt;enhanced criterion&lt;/a&gt; has its own exceptions, including equivalent controls and inline targets. It does not provide the same spacing exception.&lt;/p&gt;

&lt;p&gt;These are requirements for pointer targets, not only touchscreens. CSS pixels also are not physical screen pixels. Passing one target-size criterion does not establish overall WCAG conformance, and a minimum is not a guarantee of comfortable operation.&lt;/p&gt;

&lt;p&gt;For this example, I chose a roomier button because repeated table actions deserve more than a borderline pass. In another interface, I would evaluate density, user needs, and observed errors before choosing the component’s dimensions. I would record an exception when relying on one, rather than turning “there is some whitespace” into an accessibility claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Place Controls Along the Task
&lt;/h2&gt;

&lt;p&gt;Distance only becomes meaningful when we identify a starting point.&lt;/p&gt;

&lt;p&gt;In an illustrative form, a person completes the final field near the bottom, then travels back to a small Save control in the header. Placing a clearly labeled action after the fields offers a more direct continuation. That recommendation assumes the person is finishing the form in that sequence. It is not evidence that every page needs a fixed bottom bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0o8zwt7q562kbkeiqp4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0o8zwt7q562kbkeiqp4.webp" alt="Hypothetical forms show a long return from the last field to a header Save button and a nearby Save action below the fields." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. A task-relative placement comparison. The alternative changes both placement and target size, so it cannot isolate the effect of either variable. These are invented layouts, not tested product results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The distinction matters on complex screens. A report builder might have several entry points: editing a title, changing a filter, selecting rows, or inspecting a chart. There may be no single last field and no universal best location for Save.&lt;/p&gt;

&lt;p&gt;I would observe the common sequences, identify the actions that follow each one, and preserve a predictable home for global controls. Contextual actions can appear near a selection when that relationship is understandable. They should not move unpredictably just because the cursor has moved.&lt;/p&gt;

&lt;p&gt;A nearby action also needs enough context to be the correct action. If Save actually publishes changes immediately, the label and review flow deserve attention before celebrating a shorter pointer journey. Reducing travel should not remove a step that helps someone understand an important consequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Touch Does Not Give Us a Permanent Starting Cursor
&lt;/h3&gt;

&lt;p&gt;On a desktop, pointer position can be observed. On a phone, the previous touch does not reveal where a hand is resting now or how the device is being held.&lt;/p&gt;

&lt;p&gt;Hoober’s &lt;a href="https://www.uxmatters.com/mt/archives/2017/03/design-for-fingers-touch-and-people-part-1.php" rel="noopener noreferrer"&gt;2017 account of his touch research&lt;/a&gt; emphasizes variation in how people hold and interact with devices. His &lt;a href="https://www.smashingmagazine.com/2022/02/fitts-law-touch-era/" rel="noopener noreferrer"&gt;2022 discussion&lt;/a&gt; challenges the assumption that a mouse-based starting position transfers neatly to touch.&lt;/p&gt;

&lt;p&gt;My practical response is to test the actual task on devices, including changes of grip, scrolling, and use with the on-screen keyboard. A bottom action might help one sequence but become obscured or awkward in another. A convenient position in a static phone mockup is a hypothesis about use, not proof of reachability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen Edges Need a Boundary That Really Stops the Pointer
&lt;/h2&gt;

&lt;p&gt;The familiar edge advantage comes from a constrained pointer: if it cannot move beyond a display boundary, a target that reaches that boundary can be harder to overshoot in that direction. This is the desktop case described in &lt;a href="https://www.nngroup.com/articles/fitts-law/" rel="noopener noreferrer"&gt;Budiu’s explanation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, a card edge, browser content edge, or window edge is not necessarily a pointer barrier. Hoober’s &lt;a href="https://www.smashingmagazine.com/2022/02/fitts-law-touch-era/" rel="noopener noreferrer"&gt;touch-era discussion&lt;/a&gt; is useful here because it questions whether the assumed boundary exists at all. A neighboring monitor can also turn what appears to be a screen edge into a crossing point.&lt;/p&gt;

&lt;p&gt;For a website, I would not defend a tiny corner control by calling it an “infinite target.” I would inspect the actual environment. The browser may have chrome around the page, the control may be inset, and touch interaction has no equivalent cursor clamp. A large enough close button remains a sensible design choice without a borrowed geometric excuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Right Action Easier Without Making Mistakes Expensive
&lt;/h2&gt;

&lt;p&gt;Fitts’s Law describes a relationship in movement performance. It does not tell a product team which behavior should be encouraged.&lt;/p&gt;

&lt;p&gt;Imagine a cancellation page with a prominent Keep subscription button and a tiny, isolated cancellation link. The arrangement might make one action easier to acquire, but that does not make it a better experience for someone who came to cancel. My standard is that the person’s intended legitimate action should remain clear and operable.&lt;/p&gt;

&lt;p&gt;Similarly, a destructive action should not be made inaccessible in the name of caution. I would preserve a usable target, distinguish the consequence, separate it from routine actions, and provide a suitable review or recovery mechanism. Confirmation should earn its interruption through the risk involved.&lt;/p&gt;

&lt;p&gt;Input behavior matters too. W3C’s &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-cancellation.html" rel="noopener noreferrer"&gt;Pointer Cancellation guidance&lt;/a&gt; explains why activation on release, using ordinary click behavior where appropriate, supports cancellation. Executing an ordinary action immediately on pointer-down can remove that opportunity. Enlarging the button would not repair that interaction decision.&lt;/p&gt;

&lt;p&gt;And if a task requires dragging, enlarging the handle is only one improvement. &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html" rel="noopener noreferrer"&gt;WCAG 2.2’s Dragging Movements criterion&lt;/a&gt; requires a single-pointer alternative without dragging, subject to its exceptions. Keyboard support alone is not necessarily that alternative. A reorderable list might offer clickable Move up and Move down actions as well as dragging.&lt;/p&gt;

&lt;p&gt;These checks expand the review beyond whether the target can be hit. They ask whether an attempted action can be controlled and whether the task remains available through another workable interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk Through One Complete Redesign
&lt;/h2&gt;

&lt;p&gt;Return to our hypothetical report table. Each row has a report title, an Edit icon, and a Delete icon. The icons are tightly packed. Editing opens a panel whose Save control sits above a long form. A success message appears in a corner after saving.&lt;/p&gt;

&lt;p&gt;I would begin with the task “update this report’s notes and return to the list.” That gives the review a definite purpose without assuming every table interaction has the same path.&lt;/p&gt;

&lt;p&gt;First, I would make Edit an identifiable button with its own usable area. If the row title opens the report, I would keep that link distinct from the edit action. Making the entire row clickable while also placing several controls inside it would require careful interaction design; it should not be a shortcut for avoiding that distinction.&lt;/p&gt;

&lt;p&gt;Second, I would separate deletion from routine editing. I might place it in an explicitly labeled actions menu, depending on how often it is needed. This is an example to test, not a universal endorsement of hiding secondary actions.&lt;/p&gt;

&lt;p&gt;Third, I would place Save after the relevant fields or provide a stable action region that stays usable with zoom and the mobile keyboard. Validation messages should not move the button into the path of an already-started click.&lt;/p&gt;

&lt;p&gt;Finally, I would return useful feedback near the work: identify which report was saved and preserve a clear way back to the list. That last decision connects target acquisition to the feedback concerns from the previous installment.&lt;/p&gt;

&lt;p&gt;The redesign changes several things. If a usability session improves, I would not attribute the entire gain to Fitts’s Law. Labels, grouping, placement, and feedback all contribute. The law helped expose one class of friction; the finished task still needs evaluation as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Successful Use, Not Just Faster Clicking
&lt;/h2&gt;

&lt;p&gt;Before testing, define the question. Are you studying pointing movement under controlled conditions, or assessing a complete workflow? Both are useful, but they need different measurements.&lt;/p&gt;

&lt;p&gt;For the report example, I would record whether people select the intended control, how often they activate a neighbor, whether they correct a mistake, and whether the requested edit is successfully completed. I would inspect task time alongside those outcomes. Faster completion of the wrong action is not a success.&lt;/p&gt;

&lt;p&gt;If the specific question concerns geometry, vary size or distance while holding other features reasonably stable. If you change labels and layout together, report the result as a design comparison rather than isolating a motor-performance effect you did not measure.&lt;/p&gt;

&lt;p&gt;MacKenzie’s &lt;a href="https://www.yorku.ca/mack/hci1992.html" rel="noopener noreferrer"&gt;research review&lt;/a&gt; documents how device, task, selection method, and treatment of errors complicate comparisons. That is a reason to describe test conditions carefully and avoid importing a coefficient from an unrelated experiment.&lt;/p&gt;

&lt;p&gt;For practical product work, I would include people who use different input methods and people with relevant access needs, then test the interface in the states the task creates: validation errors, expanded menus, zoomed text, loading content, and an open keyboard. Automated size checks can find geometric failures. They cannot establish whether the action is understandable or whether the user can recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Review You Can Use on the Next Screen
&lt;/h2&gt;

&lt;p&gt;This is my proposed review sequence, not a validated research instrument:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the intended action.&lt;/strong&gt; What is the person trying to accomplish, and what makes the correct control recognizable?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace the approach.&lt;/strong&gt; What precedes this action? Is the assumed pointer or hand position supported by the task?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the real target.&lt;/strong&gt; Which pixels activate it? Check both dimensions, padding, clipping, and overlap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect its neighbors.&lt;/strong&gt; What happens after a near miss? Are adjacent actions distinguishable and sufficiently separated?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the changing states.&lt;/strong&gt; Does the target stay usable when content loads, errors appear, the viewport narrows, or the keyboard opens?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete and recover.&lt;/strong&gt; Can the person confirm the result, cancel an accidental attempt, and use an appropriate alternative input method?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful review ends with a specific change and a way to evaluate it. “Increase the Edit button’s interactive area and check adjacent-action errors in the row-edit task” is more actionable than “apply Fitts’s Law.”&lt;/p&gt;

&lt;p&gt;My position is straightforward: let the interface absorb avoidable precision work. Give the intended action enough room, place it in a sensible sequence, and make its boundaries clear. Then watch someone complete the task. The value of the geometry is in the use it supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Budiu, R. (2022, July 31). &lt;a href="https://www.nngroup.com/articles/fitts-law/" rel="noopener noreferrer"&gt;&lt;em&gt;Fitts's law and its applications in UX&lt;/em&gt;&lt;/a&gt;. Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;Fitts, P. M. (1954). The information capacity of the human motor system in controlling the amplitude of movement. &lt;em&gt;Journal of Experimental Psychology, 47&lt;/em&gt;(6), 381-391. &lt;a href="https://doi.org/10.1037/h0055392" rel="noopener noreferrer"&gt;https://doi.org/10.1037/h0055392&lt;/a&gt;. Historical discussion above also draws on MacKenzie's accessible 1992 review.&lt;/p&gt;

&lt;p&gt;Hoober, S. (2017, March 6). &lt;a href="https://www.uxmatters.com/mt/archives/2017/03/design-for-fingers-touch-and-people-part-1.php" rel="noopener noreferrer"&gt;&lt;em&gt;Design for fingers, touch, and people, part 1&lt;/em&gt;&lt;/a&gt;. UXmatters.&lt;/p&gt;

&lt;p&gt;Hoober, S. (2022, February 9). &lt;a href="https://www.smashingmagazine.com/2022/02/fitts-law-touch-era/" rel="noopener noreferrer"&gt;&lt;em&gt;Fitts’ law in the touch era&lt;/em&gt;&lt;/a&gt;. Smashing Magazine.&lt;/p&gt;

&lt;p&gt;MacKenzie, I. S. (1989). &lt;a href="https://www.yorku.ca/mack/JMB89.html" rel="noopener noreferrer"&gt;A note on the information-theoretic basis for Fitts' law&lt;/a&gt;. &lt;em&gt;Journal of Motor Behavior, 21&lt;/em&gt;, 323-330. &lt;a href="https://doi.org/10.1080/00222895.1989.10735486" rel="noopener noreferrer"&gt;DOI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;MacKenzie, I. S. (1992). &lt;a href="https://www.yorku.ca/mack/hci1992.html" rel="noopener noreferrer"&gt;Fitts' law as a research and design tool in human-computer interaction&lt;/a&gt;. &lt;em&gt;Human-Computer Interaction, 7&lt;/em&gt;(1), 91-139. &lt;a href="https://doi.org/10.1207/s15327051hci0701_3" rel="noopener noreferrer"&gt;DOI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;MacKenzie, I. S., &amp;amp; Buxton, W. (1992). &lt;a href="https://www.yorku.ca/mack/CHI92.html" rel="noopener noreferrer"&gt;Extending Fitts' law to two-dimensional tasks&lt;/a&gt;. In &lt;em&gt;Proceedings of the SIGCHI Conference on Human Factors in Computing Systems&lt;/em&gt; (pp. 219-226). Association for Computing Machinery. &lt;a href="https://doi.org/10.1145/142750.142794" rel="noopener noreferrer"&gt;DOI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.-a). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-cancellation.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding SC 2.5.2: Pointer cancellation&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.-b). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding SC 2.5.5: Target size (enhanced)&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.-c). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding SC 2.5.7: Dragging movements&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.-d). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding SC 2.5.8: Target size (minimum)&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.-e). &lt;a href="https://www.w3.org/WAI/WCAG22/Techniques/css/C44" rel="noopener noreferrer"&gt;&lt;em&gt;Technique C44: Using CSS to ensure targets are at least 44 by 44 CSS pixels&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Fitts’s Law mean every button should be bigger?
&lt;/h3&gt;

&lt;p&gt;It gives a reason to examine target geometry. A particular button may need more space, a clearer label, a different location, or a combination. Enlarging everything can create crowding and scrolling that deserve their own evaluation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does doubling target size cut interaction time in half?
&lt;/h3&gt;

&lt;p&gt;No. The common model is logarithmic, and predicted movement time depends on fitted coefficients. Whole-task time also includes activities the equation does not model, such as understanding the choice and waiting for a result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is 44 pixels the WCAG 2.2 AA minimum?
&lt;/h3&gt;

&lt;p&gt;No. The AA target-size criterion uses 24 × 24 CSS pixels with specified exceptions. The enhanced AAA criterion uses 44 × 44 with its own exceptions. Check the applicable criterion in full.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a small icon have a large touch target?
&lt;/h3&gt;

&lt;p&gt;Yes. Make the surrounding control interactive, keep its area separate from neighboring actions, and communicate that it is a control. Test the actual padded region, not just the icon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should the main action always go at the bottom of a phone screen?
&lt;/h3&gt;

&lt;p&gt;No. Placement should follow the task and remain usable across device states and input methods. A bottom position is a candidate to test, not a universal consequence of Fitts’s Law.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do these illustrations show measured improvements?
&lt;/h3&gt;

&lt;p&gt;No. They are original explanatory illustrations. The numerical table is a worked calculation, and the form and report examples are hypothetical. No conversion gains or participant results are claimed.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>fittsslaw</category>
      <category>interactiondesign</category>
      <category>a11y</category>
    </item>
    <item>
      <title>PHP2Shell: What Happened, Why It Happened, and What to Do Now</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Sun, 01 Nov 2026 14:32:14 +0000</pubDate>
      <link>https://dev.to/rverwey/php2shell-what-happened-why-it-happened-and-what-to-do-now-10do</link>
      <guid>https://dev.to/rverwey/php2shell-what-happened-why-it-happened-and-what-to-do-now-10do</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/php2shell-php-security-crisis-october-2026" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The immediate answer
&lt;/h2&gt;

&lt;p&gt;The PHP security emergency unfolding on October 21, 2026 concerns &lt;strong&gt;PHP2Shell&lt;/strong&gt;, two critical vulnerabilities in the PHP runtime: &lt;strong&gt;CVE-2026-109618&lt;/strong&gt; and &lt;strong&gt;CVE-2026-109619&lt;/strong&gt;. Both can enable unauthenticated remote code execution under exploitable conditions. PHP has released fixes. If you operate a PHP website, verify the PHP runtime actually serving its requests and update it immediately. A WordPress, Drupal, or plugin update alone is not the same operation. &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP's release advisory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For operators in a hurry:&lt;/strong&gt; establish an owner for every PHP deployment, apply a verified fixed package or image, replace old running workers, and investigate suspicious activity independently of patching. If you cannot remediate an exposed application promptly, consider isolating it from public access. That isolation recommendation is also present in the &lt;a href="https://www.enisa.europa.eu/news/joint-statement-on-critical-php-vulnerabilities" rel="noopener noreferrer"&gt;European agencies' joint statement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a developing incident, not a completed forensic history. This article is a &lt;strong&gt;publication-day assessment dated October 21, 2026, Eastern Time&lt;/strong&gt;. It separates maintainer-confirmed behavior, researchers' demonstrations, providers' observations, and my own engineering recommendations. It does not claim to have tested an exploit or investigated a victim organization. The diagrams are original explanatory illustrations, not screenshots of attacks or measurements of compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened, and what remains unproven
&lt;/h2&gt;

&lt;p&gt;Calling this “the PHP hack” compresses several different events into one phrase. Researchers established serious flaws in PHP. Maintainers coordinated emergency fixes. Exploit demonstrations and public attention accelerated. Providers began deploying defenses, while some operators temporarily withdrew services.&lt;/p&gt;

&lt;p&gt;These categories matter because each answers a different question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence category&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;th&gt;What it does not establish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A vulnerable version is installed&lt;/td&gt;
&lt;td&gt;A remediation or vendor-validation task exists&lt;/td&gt;
&lt;td&gt;That an attacker reached the installation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A researcher demonstrates execution&lt;/td&gt;
&lt;td&gt;A particular setup can be exploited&lt;/td&gt;
&lt;td&gt;That every deployment is equally exploitable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A security provider sees attack traffic&lt;/td&gt;
&lt;td&gt;Attempts are reaching monitored infrastructure&lt;/td&gt;
&lt;td&gt;That every attempt succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A service goes offline&lt;/td&gt;
&lt;td&gt;Availability changed&lt;/td&gt;
&lt;td&gt;Whether the cause was compromise or precaution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forensic evidence confirms attacker activity&lt;/td&gt;
&lt;td&gt;A specific system was compromised&lt;/td&gt;
&lt;td&gt;The total number of victims worldwide&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cloudflare says it observed attacks against its customers and changes to payloads intended to evade protection. It says its PHP2Shell protections became available by default for customers using its free or paid WAF at &lt;strong&gt;03:02 UTC on October 21&lt;/strong&gt;. Those are the provider's observations and protection claims, not a census of successful breaches. &lt;a href="https://blog.cloudflare.com/php-vulnerabilities-waf-protection" rel="noopener noreferrer"&gt;Cloudflare's incident report&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wiz reports demonstrating unauthenticated code execution against the WordPress, Drupal, and Joomla container images it tested. It also describes memory disclosure and multiple variants grouped under the two CVEs. This is significant evidence of practical exploitability, but a demonstration against a named application's deployment does not mean that application's own code introduced the flaw. &lt;a href="https://www.wiz.io/blog/php2shell-php-rce" rel="noopener noreferrer"&gt;Wiz Research&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also documented precautionary outages. Email-alias service addy.io says it took its website and web application offline while updating, then restored them; email delivery continued. Its explanation describes a protective shutdown, not a confirmed intrusion. &lt;a href="https://addy.io/blog/addy-io-went-offline-for-the-php2shell-vulnerability/" rel="noopener noreferrer"&gt;addy.io's account&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could not establish a reliable global victim count from the primary sources reviewed.&lt;/strong&gt; Claims that a percentage of “the internet was hacked” confuse technology adoption with confirmed compromise. We also should not infer a particular criminal group, motive, or data-theft campaign without evidence tying it to an intrusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The disclosure timeline
&lt;/h2&gt;

&lt;p&gt;The PHP Foundation traces early signs to 2024 audit follow-up, before a concrete execution path was established. It attributes delays to resource constraints and difficult compatibility decisions. Reports from Calif, OpenAI, Wiz, and others later clarified the impact. &lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;PHP Foundation retrospective&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Documented development&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;Early signs of the OOM problem emerge during audit follow-up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;August 2026&lt;/td&gt;
&lt;td&gt;Concrete reports clarify the impact and intensify remediation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;October 20&lt;/td&gt;
&lt;td&gt;CVE identifiers circulate; PHP issues advance notice.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;October 21&lt;/td&gt;
&lt;td&gt;Emergency releases and technical advisories become public.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Foundation describes leaked identifiers, an accidentally published demonstration, and premature distributor images. The planned October 22 release moved forward. This article reports that account without independently adjudicating participants' responsibility. &lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;Foundation disclosure account&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz557l0fhhroh9grc9bx9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz557l0fhhroh9grc9bx9.webp" alt="Illustrated sequence: early signs in 2024, concrete reports in August 2026, disrupted disclosure on October 20, and fixes released October 21." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. A deliberately coarse timeline. Sources: the Foundation account above and the release notices below. It shows sequence, not the duration of confirmed malicious activity.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The pre-announcement scheduled release for &lt;strong&gt;14:00 UTC, or 10:00 a.m. EDT&lt;/strong&gt;. Publication-day summaries differ on the precise earlier availability time: CERT-EU records 13:30 UTC; ENISA records 13:40 UTC. The defensible statement is that fixes became available October 21, before the announced deadline, rather than presenting one minute as universally settled. &lt;a href="https://discourse.thephp.foundation/t/announce-upcoming-critical-php-security-release/6251" rel="noopener noreferrer"&gt;PHP advance notice&lt;/a&gt;, &lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018" rel="noopener noreferrer"&gt;CERT-EU&lt;/a&gt;, &lt;a href="https://www.enisa.europa.eu/news/joint-statement-on-critical-php-vulnerabilities" rel="noopener noreferrer"&gt;ENISA&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these vulnerabilities are unusually consequential
&lt;/h2&gt;

&lt;p&gt;PHP runs on the server. It processes requests and executes application code before sending a response to a browser. A problem in that runtime can therefore sit underneath many unrelated products. The UK NCSC explicitly tells organizations to inventory PHP throughout their infrastructure, including appliances and third-party services that bundle it. &lt;a href="https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php" rel="noopener noreferrer"&gt;NCSC alert&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important architectural question is &lt;strong&gt;where the security boundary sits&lt;/strong&gt;. An application login protects application actions after the application can enforce that login. It cannot reliably protect a vulnerable runtime operation that happens earlier. Likewise, a protection implemented inside PHP depends on PHP safely reaching that protection.&lt;/p&gt;

&lt;p&gt;This is why I would challenge the reassuring statement “our admin page requires multifactor authentication.” MFA remains valuable, but it answers a different question. The relevant question here is whether untrusted network input can reach vulnerable PHP processing at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  CVE-2026-109618: interruption during partially initialized state
&lt;/h3&gt;

&lt;p&gt;PHP's OOM advisory explains that &lt;code&gt;longjmp()&lt;/code&gt; can abort processing after an allocation failure while internal request data is only partly initialized. Cleanup can then consume invalid state. It documents unauthenticated remote execution on Apache &lt;code&gt;mod_php&lt;/code&gt;, including before application code runs. The fix changes OOM handling to defer a soft failure to a safe execution point, with a hard exit at a higher threshold. &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;Maintainer OOM advisory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A useful conceptual model is an interrupted handoff: one part of the engine has made a structure reachable, but has not finished making it safe for another part to use. Cleanup assumes the handoff completed. This analogy explains the lifetime problem; it is not a description of a payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  CVE-2026-109619: a later request uses yesterday's pointer
&lt;/h3&gt;

&lt;p&gt;The second advisory describes request-owned memory that is freed while a reusable worker retains references to it. An operation in a later request can consume that stale state before normal initialization replaces it. Exploitation requires a suitable producer and consumer, with requests reaching the same worker. The advisory confirms remote execution through session-lifecycle paths on both &lt;code&gt;mod_php&lt;/code&gt; and PHP-FPM. &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;Maintainer stale-state advisory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of a reusable worker as a desk used for successive jobs. Clearing the old paperwork is not enough if the desk still contains instructions to use an old location. That location may now contain something else. The mistake is not simply “too much traffic”; it is treating an invalid reference as trustworthy state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrkgg4280588m6i1b6yv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrkgg4280588m6i1b6yv.webp" alt="Two broken handoffs shown with layered blue structures: interrupted initialization reaches cleanup; freed memory leaves a stale reference for a later request." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Simplified models of the two maintainer advisories. The sculptural forms illustrate lifecycle stages, not a runnable exploit or a complete list of variants.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration differences affect the path, not the patch obligation
&lt;/h3&gt;

&lt;p&gt;The PHP project explicitly includes PHP-FPM, &lt;code&gt;mod_php&lt;/code&gt;, CGI, FrankenPHP, and LiteSpeed in its warning. It assigns the highest risk to &lt;code&gt;mod_php&lt;/code&gt;. &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP release advisory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a publication-day documentation inconsistency worth preserving: the OOM advisory says a complete FPM exploit was not demonstrated for that SAPI, while the Foundation's narrative says OOM-related FPM execution was confirmed through application paths. The separate stale-state advisory clearly confirms an FPM session-lifecycle exploit. &lt;strong&gt;FPM must not be treated as safe.&lt;/strong&gt; Exact demonstration claims should name their CVE and source. &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;OOM advisory&lt;/a&gt;, &lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;Foundation account&lt;/a&gt;, &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;stale-state advisory&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is affected, and which versions contain fixes?
&lt;/h2&gt;

&lt;p&gt;Both issues are rated &lt;strong&gt;CVSS v4.0 9.2, Critical&lt;/strong&gt;. A severity score is not a probability that your server has been compromised. The supported-branch remediation thresholds published for this incident are: &lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018" rel="noopener noreferrer"&gt;CERT-EU advisory&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PHP branch&lt;/th&gt;
&lt;th&gt;First upstream release with these fixes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.2.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.3.36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.4.27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.5.12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Earlier builds in each branch require remediation or a verified vendor backport.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmcrdc2v51k2tka9i746z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmcrdc2v51k2tka9i746z.webp" alt="A joined blue slab displays the fixed releases: 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Older branches require a verified vendor fix or migration." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Publication-day patch reference. Source: CERT-EU. These are minimum fixed releases for this incident, not a recommendation to avoid later security updates.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PHP also supplied minimal source backports for 7.4, 8.0, and 8.1. That exceptional assistance does not restore normal upstream support. Distribution and commercial support packages may use their own revision numbers, so compare the complete package revision with the vendor's advisory rather than rejecting or accepting it solely from the upstream-looking number. &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP's update instructions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PHP's published lifecycle gives branches two years of active support followed by two years of security support. An emergency fix is therefore a reason to review the next upgrade deadline, not to reset your support assumptions. &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;PHP support policy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wiz traces affected code as far back as PHP 5.4, while describing different requirements across variants. Treat legacy systems as an inventory priority; do not interpret “our version is old” as immunity. &lt;a href="https://www.wiz.io/blog/php2shell-php-rce" rel="noopener noreferrer"&gt;Wiz technical research&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For embedded products, consult the vendor and the &lt;a href="https://github.com/NCSC-NL/php2shell" rel="noopener noreferrer"&gt;NCSC-NL coordination repository&lt;/a&gt;. A missing product entry is not evidence of safety. Record the vendor's actual determination, its date, and the affected product version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our take: could this have been prevented?
&lt;/h2&gt;

&lt;p&gt;The honest answer has three parts. Runtime design and security governance could have reduced the chance or duration of the vulnerability. Deployment controls could have reduced exposure. Containment could have reduced damage after exploitation. None supports a claim that an individual administrator could have patched an unavailable release.&lt;/p&gt;

&lt;p&gt;The following is my engineering analysis, not a claim that these measures were tested against every PHP2Shell variant.&lt;/p&gt;

&lt;h3&gt;
  
  
  What AI changes, and what it does not
&lt;/h3&gt;

&lt;p&gt;Wiz describes frontier AI as helping researchers turn difficult, longstanding engine flaws into practical demonstrations. That is a claim about the research process; it does not establish that these defects were originally written by AI or that every malicious attempt is AI-operated. &lt;a href="https://www.wiz.io/blog/php2shell-php-rce" rel="noopener noreferrer"&gt;Wiz's research account&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My interpretation is that faster discovery increases the value of a fast, reliable response system. A tool that finds more defects does not automatically supply the maintainers, compatibility decisions, distribution coordination, or deployment capacity needed to remove them. Defenders should measure time to a verified fix, not celebrate report volume as though it were the same outcome.&lt;/p&gt;

&lt;p&gt;Nor should “AI found it” become a reason to skip validation. A security report still needs reproducibility, a clear affected configuration, an impact assessment, and a reviewed correction. The useful investment pairs better discovery with the human and operational capacity to act on credible findings.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Treat exceptional control flow as part of the security model
&lt;/h3&gt;

&lt;p&gt;Most application tests ask whether valid input produces the correct output. Runtime tests also need to ask what remains true when execution stops at the worst possible point. Allocation failure, timeout, error callbacks, shutdown hooks, and worker reuse deserve explicit attention.&lt;/p&gt;

&lt;p&gt;For systems with manual memory management, I would make ownership and initialization invariants reviewable: who owns a structure, when it becomes visible, which exit paths can interrupt its construction, and who may still reference it after teardown. Fault injection should deliberately fail allocations across those transitions. Sanitizers and fuzzing can help reveal violations, but passing a test campaign cannot prove that all memory-safety defects are absent.&lt;/p&gt;

&lt;p&gt;A useful review exercise is to walk each lifecycle backward. If cleanup runs now, what does it assume has already happened? If a second request starts now, what state does it inherit? That method targets the kind of boundary error illustrated here without needing a weaponized reproduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Give difficult security decisions an escalation deadline
&lt;/h3&gt;

&lt;p&gt;When every proposed fix has compatibility or availability costs, consensus can become a waiting room. My recommendation is to assign one accountable decision owner, require written tradeoffs, set a deadline for escalation, and reserve engineering capacity for high-impact unresolved risks.&lt;/p&gt;

&lt;p&gt;The goal is not to pretend that maintainers can fix everything instantly. It is to prevent “we cannot agree on the least harmful change” from silently becoming a long-term security policy. An organization that depends on an open-source runtime should fund maintenance and review capacity as part of its operating cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Make emergency rollout a rehearsed product capability
&lt;/h3&gt;

&lt;p&gt;A team should be able to answer where PHP runs, who can update it, how a fixed build enters production, how old workers are retired, and what proves completion. Those answers should exist before a headline arrives.&lt;/p&gt;

&lt;p&gt;I would rehearse a runtime update using a small representative deployment, then expand with observable health checks. The rollback plan must be explicit: reverting to a known-vulnerable runtime may restore availability while reopening the security exposure. A better fallback may be isolation or a reduced service built on a verified fixed image.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Reduce what the PHP process can reach
&lt;/h3&gt;

&lt;p&gt;Successful code execution has the privileges and access of the compromised process; it is not automatically root access to every connected system. That distinction creates a concrete design opportunity. Use narrowly scoped database accounts, separate service identities, limited filesystem write access, and restricted network paths.&lt;/p&gt;

&lt;p&gt;For example, a public content site should not need a database credential that can administer every tenant. A web container should not need a mounted container-engine socket. A content upload directory should not become an unrestricted program-execution location. These are examples of reducing consequences, not assertions about what happened to a particular victim.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64dij555k4jc1xwhs2pa.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64dij555k4jc1xwhs2pa.webp" alt="A PHP worker connects through access boundaries to files, credentials, and a network. Limiting permitted access can reduce potential impact." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Original defensive threat model. Orange paths show possible access if a process is compromised. They do not assert lateral movement, root access, or data theft in this incident.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do if you think you are impacted
&lt;/h2&gt;

&lt;p&gt;Run two tracks together: &lt;strong&gt;remove the vulnerability&lt;/strong&gt; and &lt;strong&gt;determine whether it was used&lt;/strong&gt;. Do not make one the completion criterion for the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: identify the real runtime and the accountable operator
&lt;/h3&gt;

&lt;p&gt;Start with public websites, but include internal tools, staging sites, old subdomains, disaster-recovery instances, containers, and appliances. For each deployment, record its hostname, environment, owner, web-server integration, PHP version, package revision or image digest, and exposure path. The NCSC's guidance includes both infrastructure inventory and monitoring vendors for bundled-PHP updates. &lt;a href="https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php" rel="noopener noreferrer"&gt;NCSC alert&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With authorized shell access, these read-only commands provide a starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-v&lt;/span&gt;
php &lt;span class="nt"&gt;--ini&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They report the invoked command-line binary and its configuration. They do &lt;strong&gt;not&lt;/strong&gt; prove that Apache, an FPM pool, another container, or every node behind a load balancer is using that binary. PHP documents both switches in its &lt;a href="https://www.php.net/manual/en/features.commandline.options.php" rel="noopener noreferrer"&gt;command-line reference&lt;/a&gt;. Its &lt;a href="https://www.php.net/manual/en/configuration.file.php" rel="noopener noreferrer"&gt;configuration documentation&lt;/a&gt; also explains that configuration loading depends on how PHP runs.&lt;/p&gt;

&lt;p&gt;Use authenticated administration or deployment inspection to establish the web runtime. Avoid leaving a public diagnostic page online: &lt;code&gt;phpinfo()&lt;/code&gt; can reveal configuration, paths, headers, and environment information. &lt;a href="https://www.php.net/manual/en/function.phpinfo.php" rel="noopener noreferrer"&gt;PHP's phpinfo documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On managed hosting, ask for a precise answer: “Which package revision fixes both CVEs on my account, when was it deployed, and have all serving workers been replaced?” A generic message that the provider is monitoring the situation is not evidence that your instance is patched. WordPress explicitly tells site owners to check with their host and update PHP. &lt;a href="https://wordpress.org/news/2026/10/critical-php-security-release/" rel="noopener noreferrer"&gt;WordPress security notice&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: patch what is running, not just what is stored
&lt;/h3&gt;

&lt;p&gt;My recommended completion record contains the old revision, the fixed revision, the deployment time, the worker replacement time, and the verification result. Keep one record per independently deployed service rather than a single checkbox for the whole estate.&lt;/p&gt;

&lt;p&gt;For containers, rebuild or select a verified fixed image and redeploy the application. Updating the host's PHP package does not establish what is inside an image. Cloud.gov's incident instructions specifically distinguish its PHP buildpack from customer-managed container images and tell the latter group to update and redeploy. &lt;a href="https://docs.cloud.gov/news/2026/10/21/critical-php-vulnerabilities/" rel="noopener noreferrer"&gt;Cloud.gov guidance&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For long-running workers, verify replacement rather than assuming that a package installer did it. PHP's configuration-loading behavior explains why editing a file alone is insufficient for server modules. &lt;a href="https://www.php.net/manual/en/configuration.file.php" rel="noopener noreferrer"&gt;PHP configuration lifecycle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then check login, sessions, uploads, scheduled processing, and the application's main business transaction. These are practical smoke checks, not exploit tests. Verify every replica and the images used for future scale-out, or the next restart may reintroduce an old build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: if patching is blocked, contain exposure deliberately
&lt;/h3&gt;

&lt;p&gt;Prefer restricting access before requests reach PHP. A maintenance page rendered by the vulnerable application is a poor isolation boundary. Use the load balancer, reverse proxy, network controls, or an independently served static page, and account for alternate hostnames and direct-origin access.&lt;/p&gt;

&lt;p&gt;The official configuration workarounds are partial. In abbreviated form, they include administratively enforcing &lt;code&gt;memory_limit=-1&lt;/code&gt; for the OOM path, disabling session upload progress before parsing, and disabling &lt;code&gt;fatal_error_backtraces&lt;/code&gt; on PHP 8.5 for a specific stale-state path. Workers must be restarted. The advisory warns that underlying allocation failures and other variants remain possible; &lt;code&gt;RLIMIT_AS&lt;/code&gt; and &lt;code&gt;RLIMIT_DATA&lt;/code&gt; are inappropriate substitutes. &lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018" rel="noopener noreferrer"&gt;CERT-EU mitigation details&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not turn those settings into an unattended “fix everything” script. Removing PHP's normal memory ceiling changes availability risk. Request limits must be evaluated against real application behavior and the frontend's handling of decoded data. Use the linked vendor instructions for the full conditions, and assign an expiry and patch owner to temporary measures.&lt;/p&gt;

&lt;p&gt;A WAF can buy time, but it is a separate control. Cloudflare itself says customers should still patch. Confirm that the applicable rules are enabled and that requests cannot bypass the protected entry point. &lt;a href="https://blog.cloudflare.com/php-vulnerabilities-waf-protection" rel="noopener noreferrer"&gt;Cloudflare guidance&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: investigate evidence of exploitation
&lt;/h3&gt;

&lt;p&gt;Begin by preserving the information you will need to reconstruct events. That can include proxy and web-server logs, PHP errors, host process telemetry, authentication events, outbound connections, deployment history, and relevant cloud audit records. Record collection time and timezone, protect the originals, and restrict access to evidence that contains sensitive data.&lt;/p&gt;

&lt;p&gt;Elastic's PHP2Shell detection discussion emphasizes behavior such as unexpected processes launched by web services and suspicious follow-on execution. Those are useful investigative signals, but a detection rule needs the appropriate telemetry and can have blind spots. &lt;a href="https://www.elastic.co/security-labs/threat-command/php2shell-php-rce-detection" rel="noopener noreferrer"&gt;Elastic Security Labs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My recommended investigation questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did a PHP or web-server worker start an unexpected shell, interpreter, or downloader?&lt;/li&gt;
&lt;li&gt;Did executable files, configuration, scheduled tasks, or administrator accounts change outside a known deployment?&lt;/li&gt;
&lt;li&gt;Did the service make unusual outbound connections or access secrets it normally leaves untouched?&lt;/li&gt;
&lt;li&gt;Do memory errors, worker crashes, or unusual requests correlate with those stronger signals?&lt;/li&gt;
&lt;li&gt;What evidence is missing because logging was disabled, incomplete, or already rotated?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A memory error by itself is not proof of intrusion. A suspicious request by itself is not proof of successful execution. Conversely, a quiet access log does not prove no compromise: it may not contain request bodies, process activity, or evidence from an alternate route.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: if compromise is suspected, recover trust
&lt;/h3&gt;

&lt;p&gt;Escalate to the incident-response owner or a qualified responder. Isolate affected systems while balancing evidence preservation and ongoing harm. Avoid casually wiping or rebooting a suspected host before deciding whether volatile evidence is needed; do not leave an active intrusion connected merely to collect more logs.&lt;/p&gt;

&lt;p&gt;For confirmed compromise, my recommendation is to rebuild from trusted sources after evidence collection, fix the entry path, and evaluate every credential accessible to the affected service. Rotate or revoke exposed secrets from a clean administrative environment. Consider database credentials, API tokens, service identities, signing keys, and sessions according to what the investigation establishes. Patching a runtime does not remove an attacker's separate persistence or invalidate a copied secret.&lt;/p&gt;

&lt;p&gt;This workflow follows the broad separation of detection, response, and recovery in &lt;a href="https://csrc.nist.gov/pubs/sp/800/61/r3/final" rel="noopener noreferrer"&gt;NIST SP 800-61 Revision 3&lt;/a&gt;; the PHP-specific operational checklist here is my synthesis. Decisions about customer notification, contractual duties, or regulatory reporting should go through the organization's incident and legal teams using the actual facts and applicable jurisdiction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0fv6s655tfjn0ehfoikr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0fv6s655tfjn0ehfoikr.webp" alt="Two separate response tracks: remediate through patching, restarting, and verification; investigate through preserving evidence, assessment, and recovery." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. An original response checklist. Remediation and investigation proceed in parallel; a completed update is not a forensic clearance.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for website owners and ordinary visitors
&lt;/h2&gt;

&lt;p&gt;If you own a website but someone else manages its hosting, forward the two CVE identifiers and request instance-specific confirmation. Ask whether their response covers the origin, all PHP versions assigned to your account, and any staging or secondary sites. If they cannot provide a safe update path, discuss temporary isolation and a supported migration rather than relying on the fact that the homepage still works.&lt;/p&gt;

&lt;p&gt;If you only visit a website, you cannot patch its server from your browser. An outage or maintenance banner does not establish that your personal information was stolen. Follow the operator's official security communications and any account-specific instructions. Do not install a “PHP repair” utility offered by a random message or submit credentials to an unsolicited emergency-checking page.&lt;/p&gt;

&lt;p&gt;If an operator confirms credential or session exposure, follow its reset instructions through the legitimate site and address reused passwords on other services. This is contingent advice for confirmed or credibly suspected exposure, not a claim that every visitor to a PHP site now needs an emergency reset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I would carry into the next incident
&lt;/h2&gt;

&lt;p&gt;PHP2Shell makes a familiar operational weakness visible: a team may know its application very well while having a poor account of the runtime underneath it. The useful outcome is not simply a newer version number. It is a defensible chain of evidence from advisory, to affected asset, to fixed deployment, to verified running service, with a separate answer about possible intrusion.&lt;/p&gt;

&lt;p&gt;My standard would be straightforward: name the owner, prove the revision, verify the workers, preserve the evidence, and state what remains unknown. That is a much stronger response than either minimizing a serious flaw or describing every vulnerable site as already breached.&lt;/p&gt;

&lt;p&gt;For more context on how I approach implementation and operational tradeoffs, see &lt;a href="https://www.ryanverwey.dev/about" rel="noopener noreferrer"&gt;my development background&lt;/a&gt; and &lt;a href="https://www.ryanverwey.dev/projects" rel="noopener noreferrer"&gt;the projects behind this portfolio&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and reporting method
&lt;/h2&gt;

&lt;p&gt;Primary sources were preferred over social posts and secondary headlines. Source statements were checked on October 21, 2026, Eastern Time. The list below is a reading trail; citations beside individual claims identify their basis. Source pages can change after publication. Differences in release timing and FPM demonstration language are explicitly retained above rather than silently reconciled.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PHP Security Team. (2026, October 21). &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;Critical remote code execution vulnerabilities: update now&lt;/a&gt;. Release scope, fixed versions, and update guidance.&lt;/li&gt;
&lt;li&gt;PHP maintainers. (2026, October 21). &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;RCE via OOM corruption, GHSA-w8hw-w9fx-j79w&lt;/a&gt;. CVE-2026-109618 technical advisory.&lt;/li&gt;
&lt;li&gt;PHP maintainers. (2026, October 21). &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;RCE via stale global state, GHSA-6wmf-4rv5-p3rp&lt;/a&gt;. CVE-2026-109619 technical advisory.&lt;/li&gt;
&lt;li&gt;The PHP Foundation. (2026, October 21). &lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;Today's PHP Security Release, and How We're Strengthening PHP Security&lt;/a&gt;. Maintainer-side history and disclosure account.&lt;/li&gt;
&lt;li&gt;PHP Security Team. (2026, October 20). &lt;a href="https://discourse.thephp.foundation/t/announce-upcoming-critical-php-security-release/6251" rel="noopener noreferrer"&gt;Upcoming Critical PHP Security Release&lt;/a&gt;. Advance notice.&lt;/li&gt;
&lt;li&gt;CERT-EU. (2026, October 21). &lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018" rel="noopener noreferrer"&gt;Security Advisory 2026-018&lt;/a&gt;. Severity, versions, and mitigation conditions.&lt;/li&gt;
&lt;li&gt;ENISA and partner agencies. (2026, October 21). &lt;a href="https://www.enisa.europa.eu/news/joint-statement-on-critical-php-vulnerabilities" rel="noopener noreferrer"&gt;Joint statement on critical PHP vulnerabilities&lt;/a&gt;. Coordinated operational advice.&lt;/li&gt;
&lt;li&gt;UK National Cyber Security Centre. (2026, October 21). &lt;a href="https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php" rel="noopener noreferrer"&gt;Critical security issue affecting PHP&lt;/a&gt;. Inventory, monitoring, and isolation guidance.&lt;/li&gt;
&lt;li&gt;Tzadik, S., Tamari, S., Ohfeld, N., &amp;amp; Schwartz, T. (2026, October 21). &lt;a href="https://www.wiz.io/blog/php2shell-php-rce" rel="noopener noreferrer"&gt;PHP2Shell technical research&lt;/a&gt;. Wiz's discoveries and demonstrations.&lt;/li&gt;
&lt;li&gt;Molteni, D., Jones, D., &amp;amp; Nandwani, K. (2026, October 21). &lt;a href="https://blog.cloudflare.com/php-vulnerabilities-waf-protection" rel="noopener noreferrer"&gt;Cloudflare deploys protections against php2shell&lt;/a&gt;. Provider observations and WAF claims.&lt;/li&gt;
&lt;li&gt;addy.io. (2026, October 21). &lt;a href="https://addy.io/blog/addy-io-went-offline-for-the-php2shell-vulnerability/" rel="noopener noreferrer"&gt;Why addy.io went offline&lt;/a&gt;. First-party account of a precautionary outage.&lt;/li&gt;
&lt;li&gt;Blackbourn, J. (2026, October 21). &lt;a href="https://wordpress.org/news/2026/10/critical-php-security-release/" rel="noopener noreferrer"&gt;Critical PHP Security Release&lt;/a&gt;. Guidance for WordPress site owners.&lt;/li&gt;
&lt;li&gt;Cloud.gov. (2026, October 21). &lt;a href="https://docs.cloud.gov/news/2026/10/21/critical-php-vulnerabilities/" rel="noopener noreferrer"&gt;Action required: Critical PHP vulnerabilities&lt;/a&gt;. Buildpack and container remediation responsibilities.&lt;/li&gt;
&lt;li&gt;Elastic Security Labs. (2026, October 21). &lt;a href="https://www.elastic.co/security-labs/threat-command/php2shell-php-rce-detection" rel="noopener noreferrer"&gt;Assessing detection coverage for PHP2Shell&lt;/a&gt;. Behavioral detection considerations.&lt;/li&gt;
&lt;li&gt;NCSC-NL. (2026). &lt;a href="https://github.com/NCSC-NL/php2shell" rel="noopener noreferrer"&gt;PHP2Shell coordination repository&lt;/a&gt;. Evolving third-party product information.&lt;/li&gt;
&lt;li&gt;PHP documentation. &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;Supported versions&lt;/a&gt;, &lt;a href="https://www.php.net/manual/en/features.commandline.options.php" rel="noopener noreferrer"&gt;command-line options&lt;/a&gt;, &lt;a href="https://www.php.net/manual/en/configuration.file.php" rel="noopener noreferrer"&gt;configuration loading&lt;/a&gt;, and &lt;a href="https://www.php.net/manual/en/function.phpinfo.php" rel="noopener noreferrer"&gt;phpinfo&lt;/a&gt;. Runtime-verification background.&lt;/li&gt;
&lt;li&gt;Nelson, A., Rekhi, S., Souppaya, M., &amp;amp; Scarfone, K. (2025). &lt;a href="https://csrc.nist.gov/pubs/sp/800/61/r3/final" rel="noopener noreferrer"&gt;NIST SP 800-61 Revision 3&lt;/a&gt;. General incident-response framework, not a PHP2Shell advisory.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Cost of a Pause: Designing for the Doherty Threshold</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Mon, 19 Oct 2026 14:37:21 +0000</pubDate>
      <link>https://dev.to/rverwey/the-cost-of-a-pause-designing-for-the-doherty-threshold-4n2e</link>
      <guid>https://dev.to/rverwey/the-cost-of-a-pause-designing-for-the-doherty-threshold-4n2e</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-cost-of-a-pause-doherty-threshold-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Doherty Threshold is a response-time principle commonly summarized as keeping computer feedback below roughly 400 milliseconds so people can maintain their working rhythm. Its useful lesson is that the pause between an action and a meaningful response is part of the user experience, not simply a backend performance number.&lt;/p&gt;

&lt;p&gt;Click Save. Nothing changes.&lt;/p&gt;

&lt;p&gt;You wait a moment. Did the click register? You click again. Now the button says "Saving," but the page still shows your previous settings. You open another tab to check. When you return, a notification says "Success." Success for which version?&lt;/p&gt;

&lt;p&gt;The system might have completed its work correctly. It has still made you supervise the transaction.&lt;/p&gt;

&lt;p&gt;That is the problem I want to examine in this sixth article in &lt;strong&gt;Applied Design Theory&lt;/strong&gt;. We have discussed familiarity, choice, grouping, bias, and mental effort. Response time connects those ideas to the moment software either supports our next action or makes us stop and investigate.&lt;/p&gt;

&lt;p&gt;As a developer, I am interested in both the milliseconds and the meaning of the response. A quick animation can acknowledge a click. It cannot establish that a payment cleared, a report exists, or a change was saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A responsive interface gives people timely evidence about what happened and a dependable way to continue.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pause Costs More Than Its Duration
&lt;/h2&gt;

&lt;p&gt;Imagine a hypothetical reporting tool. A team member filters a list, selects several records, requests an export, and sends the resulting file to a colleague. Each action depends on the one before it.&lt;/p&gt;

&lt;p&gt;When the filter takes effect clearly, the person can inspect the results. When the selection remains visible, they can verify the export scope. When the export becomes available, they can finish the task.&lt;/p&gt;

&lt;p&gt;Now insert an ambiguous pause after each step. The list does not visibly acknowledge the filter. The selection count updates late. The export button stays enabled after activation. A spinner eventually appears without identifying what it is doing.&lt;/p&gt;

&lt;p&gt;There is processing time in this experience, but there is also interpretation time. The person has to decide whether to wait, repeat the action, or investigate elsewhere. Those are additional decisions that the product introduced.&lt;/p&gt;

&lt;p&gt;This connects directly to &lt;a href="https://www.ryanverwey.dev/blog/the-invisible-work-cognitive-load-ui" rel="noopener noreferrer"&gt;The Invisible Work&lt;/a&gt;. Unclear feedback asks people to remember what they attempted while inferring the current state from incomplete evidence. The screen can remain visually simple while the task becomes difficult to follow.&lt;/p&gt;

&lt;p&gt;My design question is: &lt;strong&gt;What does the person have enough evidence to do next?&lt;/strong&gt; A response that leaves that question unanswered has limited value, however quickly it appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Historical Research Actually Supports
&lt;/h2&gt;

&lt;p&gt;Doherty and Thadhani's (1982) IBM report described relationships between faster system responses and improved productivity in interactive computing. Its definition of system response covered the interval from a command to the display of a complete response. It discussed several work settings and argued that delays could disrupt the sequence of actions people intended to carry out.&lt;/p&gt;

&lt;p&gt;The report is useful historical evidence for taking subsecond interaction seriously. It is not a controlled trial of modern web applications, and its reported gains should not become a conversion forecast for a new checkout page. The source linked here is a reproduction of the report, rather than a claim to have inspected an original journal issue.&lt;/p&gt;

&lt;p&gt;The familiar 400-millisecond formulation appears in contemporary UX guidance such as Yablonski's (n.d.) description of the Doherty Threshold. I treat it as a useful prompt to examine responsiveness, not a biological switch that flips between productive and unproductive at exactly 400 milliseconds.&lt;/p&gt;

&lt;p&gt;Related guidance also uses other time ranges. Nielsen (1993) discusses approximately 0.1 seconds for a sense of immediate response, one second for continuity of thought, and ten seconds as a point at which attention becomes difficult to sustain. These are design guidelines, not permission to leave an interface silent until a timer expires.&lt;/p&gt;

&lt;p&gt;For a drag interaction, 400 milliseconds would be a conspicuous lag. For a large report, completing all the work within that interval may be unrealistic. The task determines what a useful response must contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Clocks Are Running
&lt;/h2&gt;

&lt;p&gt;For implementation reviews, I separate three intervals: time to acknowledgment, time to useful progress, and time to confirmed completion. This is my own practical distinction, not a replacement for a standardized performance metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acknowledgment&lt;/strong&gt; tells the person that their action reached the interface. The button changes state, the selected filter becomes visible, or a message identifies the pending request. It should happen promptly and should describe only what is known.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful progress&lt;/strong&gt; gives the person evidence that advances the task. Search results become available. An export enters a confirmed queue. A job reports that it has processed a particular number of records. A decorative spinner does not, by itself, establish any of those facts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmed completion&lt;/strong&gt; means the promised outcome is actually available. A report can be downloaded. A saved configuration can be read back. A server has confirmed the state on which the next action depends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiobsgrimun7ncpk16qgr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiobsgrimun7ncpk16qgr.webp" alt="Three response milestones: acknowledge the request, show useful progress, and confirm the outcome. A separate recovery path handles errors or unknown status." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These are conceptual milestones, not a measured latency chart. Acknowledgment can precede completion without pretending to be completion.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A team can improve the first interval and leave the third unchanged. That may still help users understand what is happening. It should be described honestly as better feedback, rather than a claim that the export itself became faster.&lt;/p&gt;

&lt;p&gt;Separating the intervals also makes failures easier to discuss. "The API is fast" does not explain why the button stayed unresponsive. "The spinner appeared immediately" does not explain why the report never arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Response Contract
&lt;/h2&gt;

&lt;p&gt;I use &lt;strong&gt;Response Contract&lt;/strong&gt; as a review heuristic for the promises an interface makes after an action. It has four parts: acknowledge, advance, confirm, and recover. It is an original organizing method for this article, not a validated psychological scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Acknowledge What You Received
&lt;/h3&gt;

&lt;p&gt;In our reporting example, the initial message might be "Requesting export for 48 selected records." That tells the person what the interface is attempting. "Export queued" should wait until the service has actually accepted the job.&lt;/p&gt;

&lt;p&gt;Keep the selected scope visible. If the button simply becomes a spinning icon, the user loses information at the exact moment they want reassurance. A short label can preserve the connection between the action and its purpose.&lt;/p&gt;

&lt;p&gt;Also decide which controls should remain available. Preventing repeated submission can be sensible. Freezing the entire application because one export is pending usually needs a stronger justification.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Advance With Evidence
&lt;/h3&gt;

&lt;p&gt;Once the job is accepted, show the state the system can support: queued, processing, or preparing the download. If there is a reliable completed-unit count, present it with a clear denominator.&lt;/p&gt;

&lt;p&gt;Avoid telling a story the backend cannot substantiate. A timed sequence that says "Analyzing," "Validating," and "Finishing" regardless of what the service is doing is a presentation script, not operational visibility.&lt;/p&gt;

&lt;p&gt;The user does not need every internal event. They need enough trustworthy information to understand whether waiting still makes sense and whether they can work elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Confirm the Actual Outcome
&lt;/h3&gt;

&lt;p&gt;"Ready to download" should mean the file is ready. If generation succeeded but delivery failed, say so and expose a recoverable path. An all-purpose "Done" message hides distinctions the user may need immediately.&lt;/p&gt;

&lt;p&gt;Where the outcome matters later, leave a durable record. An export entry with its scope, creation time, and download action is more useful than a toast that disappears before the person returns from another tab.&lt;/p&gt;

&lt;p&gt;The confirmation should connect to the original request. If a user changed the selection while an export ran, the resulting file must still identify the scope captured when that job was submitted.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Recover Without Making People Guess
&lt;/h3&gt;

&lt;p&gt;A timeout is especially revealing. It may mean the browser stopped waiting while the server continued working. Declaring the operation failed and encouraging an unconditional retry can create a second job.&lt;/p&gt;

&lt;p&gt;For a service that supports it, retain an operation identifier and check status before creating another request. Show "We couldn't confirm the export status" when that is the fact you have. Preserve the person's selections and explain the available next step.&lt;/p&gt;

&lt;p&gt;Recovery belongs in the contract from the start. It cannot be designed well as a generic error toast added after the successful path is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Feedback by What You Know
&lt;/h2&gt;

&lt;p&gt;The loading component should follow the evidence available about the operation. Beginning with a favorite spinner and writing the state model afterward reverses that relationship.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;pending label&lt;/strong&gt; suits a short action whose result is not yet known. A &lt;strong&gt;skeleton&lt;/strong&gt; can reserve space for content with a predictable structure. An &lt;strong&gt;indeterminate indicator&lt;/strong&gt; communicates ongoing activity without asserting a percentage. A &lt;strong&gt;determinate progress bar&lt;/strong&gt; expresses completion against a meaningful total.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8dd86654zjjbmemv0ac.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8dd86654zjjbmemv0ac.webp" alt="Three honest progress displays: known work shows 24 of 48 records processed; unknown scope shows discovering pages without a percentage; a completed phase shows upload complete while validation remains pending." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Illustrative interface states. The numbers demonstrate known units, not elapsed-time predictions or measured product performance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Myers (1985) investigated percent-done indicators and found a preference for progress feedback in the study. The paper also discusses estimation and situations where total work cannot readily be calculated. It supports taking progress information seriously; it does not establish that every inaccurate indicator is harmless.&lt;/p&gt;

&lt;p&gt;For our example, 24 of 48 records processed is meaningful. It does not necessarily mean half the elapsed time remains. One record might require substantially more work than another, and packaging the file might be a separate phase.&lt;/p&gt;

&lt;p&gt;Label what the number measures. If the total grows during discovery, use a discovered-item count or a phase label instead of pretending the original denominator was final. When a phase finishes, identify what remains before the overall task is complete.&lt;/p&gt;

&lt;p&gt;The crucial distinction is between an estimate that represents uncertain work and a number that has no relationship to the work at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animation Should Explain a Change
&lt;/h2&gt;

&lt;p&gt;Animation can connect an action to its result: an expanded row reveals its relationship to the item above it; a brief transition shows that a filter changed the visible collection. That is different from adding motion simply to occupy someone during a wait.&lt;/p&gt;

&lt;p&gt;For the reporting tool, I would use a stable status area and restrained transitions between real states. I would avoid repeatedly rearranging the result list while the person is trying to inspect it. An attractive animation can still make the task harder if it moves the target they were about to select.&lt;/p&gt;

&lt;p&gt;Loading transitions also have their own performance cost. If rendering a complex animation delays the controls it is supposed to reassure people about, the implementation has undermined the design intent. The appropriate question is whether the animation helps explain state while preserving responsiveness.&lt;/p&gt;

&lt;p&gt;Account for reduced-motion preferences, and keep essential information in text or other perceivable state. A spinning shape cannot be the only evidence that a request is pending. If the work finishes almost immediately, showing the completed result may be clearer than flashing through several loading screens.&lt;/p&gt;

&lt;p&gt;I would not hold a ready result behind an animation just to let that animation complete. Preserve the explanation where useful; release the result when it is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Interface Can Still Be Wrong
&lt;/h2&gt;

&lt;p&gt;Optimistic updates are tempting because they let a user see a proposed change before server confirmation. That can be reasonable for some reversible actions, provided the interface can reconcile failure and conflicting updates.&lt;/p&gt;

&lt;p&gt;Consider starring a report. An immediate visual change may be acceptable if rejection can be explained and the previous state restored. Now consider sending that report to a client. "Sent" before confirmation makes a much stronger promise and may cause the person to stop checking.&lt;/p&gt;

&lt;p&gt;I would review the consequence, reversibility, and recovery path before choosing optimism. Speed alone is not the acceptance criterion.&lt;/p&gt;

&lt;p&gt;Search introduces another problem: response ordering. A person types "annual," then replaces it with "monthly." If the annual request returns last and overwrites the monthly results, a fast screen has produced the wrong answer to the current question.&lt;/p&gt;

&lt;p&gt;Associate responses with the request that still represents the user's intent. Cancel obsolete client work where appropriate, and guard against applying stale results. Canceling a browser request is not proof that server work or a consequential mutation was canceled.&lt;/p&gt;

&lt;p&gt;That is where perceived performance becomes an engineering responsibility. The interface must remain coherent as requests complete in an order the designer did not draw.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the Right Response Is a Background Job
&lt;/h2&gt;

&lt;p&gt;Some tasks should not require continuous attention. A large export or site-wide audit can outlast a comfortable foreground interaction even when the system is functioning normally.&lt;/p&gt;

&lt;p&gt;In that case, my preferred design is a reliable handoff. Confirm acceptance, provide an identifiable job, explain where its status lives, and let the person continue. If notifications are offered, make their destination and behavior clear rather than assuming permission to send them.&lt;/p&gt;

&lt;p&gt;The interface must also be truthful about leaving the page. "You can close this tab" is appropriate only when the job persists independently and the result can be recovered later. A browser-only operation cannot make that promise simply because its progress display looks like a server job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevg0rn6eclafyl3ddc2g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevg0rn6eclafyl3ddc2g.webp" alt="Recovery after an interrupted export: retain the job identifier, check its status, then open the completed result, continue waiting, or offer a safe retry after confirmed failure." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A proposed recovery flow for a persistent server job. Unknown status remains distinct from confirmed failure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Returning should restore the task's meaning: what was requested, what state it is in, and what action is available now. That connects response design to &lt;a href="https://www.ryanverwey.dev/blog/the-shape-of-understanding-chunking-ui" rel="noopener noreferrer"&gt;The Shape of Understanding&lt;/a&gt;. A job history becomes useful when its information is grouped around a person's next decision.&lt;/p&gt;

&lt;p&gt;A background process is not permission to forget the user. It changes the feedback relationship from continuous observation to dependable return.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should We Ever Make People Wait on Purpose?
&lt;/h2&gt;

&lt;p&gt;Buell and Norton (2011) studied operational transparency in simulated online travel and dating services. Across their experiments, showing apparent effort could increase perceived service value, and some conditions favored a wait with visible effort over an immediate result. Those findings concern particular experiences and judgments, not a general rule that slower software earns trust.&lt;/p&gt;

&lt;p&gt;My design choice is to explain genuine work without inventing a delay to perform it theatrically. An audit can show the pages examined, the checks run, and the limits of its findings after the result is ready. A search can explain its filters and coverage without pretending to consult sources it never accessed.&lt;/p&gt;

&lt;p&gt;This still leaves room for deliberate pacing that serves the person. A review step before publishing gives someone time to inspect consequences. An undo window provides a real opportunity to reverse an action. Neither needs to masquerade as computation.&lt;/p&gt;

&lt;p&gt;The distinction I care about is whether the interval has a user-serving function. "We need confirmation before sending this to clients" is a defensible interaction requirement. "The result looks more impressive if we hide it for three seconds" deserves a much harder conversation.&lt;/p&gt;

&lt;p&gt;This is also a place to apply &lt;a href="https://www.ryanverwey.dev/blog/the-confidence-trap-cognitive-bias-ui" rel="noopener noreferrer"&gt;The Confidence Trap&lt;/a&gt;. A polished display may increase confidence without improving the underlying result. We should test whether people understand what the system actually did, not merely whether they say it feels sophisticated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback Must Reach the Person Using It
&lt;/h2&gt;

&lt;p&gt;A status label can be visible and still fail to reach someone using assistive technology. WCAG's guidance on status messages addresses updates that convey results, progress, waiting states, or errors without taking focus. Such messages need programmatic semantics so assistive technology can present them (World Wide Web Consortium, n.d.).&lt;/p&gt;

&lt;p&gt;In practice, I would give a concise status update an appropriate live region, keep ordinary progress updates polite, and test the actual behavior with a screen reader. I would not announce every processed record if that creates a continuous stream of interruptions.&lt;/p&gt;

&lt;p&gt;Keep keyboard focus predictable. Starting an export should not unexpectedly move focus to a decorative spinner. If the relevant control changes or disappears, decide where the user should continue and verify that behavior. Keep labels meaningful as state changes.&lt;/p&gt;

&lt;p&gt;Motion, color, and a tiny toast are each insufficient as the sole communication channel. A user who returns after an interruption also needs a way to inspect status without having witnessed the original transition.&lt;/p&gt;

&lt;p&gt;Fast visual feedback is only one part of responsiveness. The operational information has to remain available to the people who need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the Interaction and the Outcome Separately
&lt;/h2&gt;

&lt;p&gt;Interaction to Next Paint, or INP, measures browser interaction responsiveness through the next paint. Google's guidance classifies 200 milliseconds or less as good, assessed at the 75th percentile of page visits. It does not measure the full duration of an asynchronous export or prove that its contents are correct (Wagner &amp;amp; Pollard, 2025).&lt;/p&gt;

&lt;p&gt;For our reporting tool, I would therefore collect separate evidence: whether the interface responds promptly, how long a request takes to be accepted, when a usable file becomes available, and whether the person successfully completes the intended task.&lt;/p&gt;

&lt;p&gt;The timing definitions need to be explicit. Does "export complete" mean a worker finished writing bytes, the file is accessible, or the browser downloaded it? Those are different events. A dashboard that combines them under one label can conceal the exact delay people experience.&lt;/p&gt;

&lt;p&gt;Look at distributions and failure cases as well as averages. Separate cold requests, large selections, and representative mobile conditions where possible. A reassuring median can coexist with a painful group of users who repeatedly encounter the slow path.&lt;/p&gt;

&lt;p&gt;Then observe behavior: repeated clicks, abandoned requests, accidental duplicate jobs, and time spent checking elsewhere. These are investigation signals, not automatic proof of a particular cause. Pair them with task observation and users' explanations.&lt;/p&gt;

&lt;p&gt;This follows the discipline of &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;The Decision Tax&lt;/a&gt;: decide what outcome matters before choosing the number that will stand in for success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the Uncomfortable States Before Shipping
&lt;/h2&gt;

&lt;p&gt;Here is the walkthrough I would use with the hypothetical export flow. Each check has a consequence worth inspecting, rather than merely a component to tick off.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Complete the fast path.&lt;/strong&gt; Does a quick success appear cleanly, without an artificial wait or a confusing flash of multiple loading states?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow down acceptance.&lt;/strong&gt; Does the interface acknowledge the attempt while avoiding the unsupported claim that the job is already queued?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delay completion.&lt;/strong&gt; Can the person tell what is pending, continue appropriate work, and find the result later?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change the selection mid-request.&lt;/strong&gt; Does the completed export still identify the original scope, while the current selection remains accurate?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interrupt the connection after submission.&lt;/strong&gt; Does the product check an existing operation before suggesting a potentially duplicate action?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return after leaving.&lt;/strong&gt; Can the person identify the job, understand its current state, and take the correct next step?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use keyboard, screen reader, and reduced motion.&lt;/strong&gt; Is the same essential status available without relying on a particular visual effect?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the system does not yet support a check, that is a useful finding. A missing job-status endpoint cannot be repaired by more confident copy. A lack of idempotency cannot be solved solely by disabling a button in one browser tab.&lt;/p&gt;

&lt;p&gt;This is why I want response design discussed before the final loading animation is chosen. It exposes requirements that belong in the service and data model as much as in the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The next time someone says an application feels slow, follow one action all the way through. Watch the first acknowledgment, the evidence of progress, the confirmation, and the route back from uncertainty.&lt;/p&gt;

&lt;p&gt;You may find expensive processing. You may also find a button that appears unchanged, a success label that arrives too early, or a completed result trapped behind a presentation delay. These require different fixes.&lt;/p&gt;

&lt;p&gt;The Doherty Threshold gives us a reason to take small pauses seriously. The broader design responsibility is to make the response truthful, useful, and recoverable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the work quickly. Show what you know. Let people continue with confidence they can verify.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Buell, R. W., &amp;amp; Norton, M. I. (2011). The labor illusion: How operational transparency increases perceived value. &lt;em&gt;Management Science, 57&lt;/em&gt;(9), 1564-1579. &lt;a href="https://doi.org/10.1287/mnsc.1110.1376" rel="noopener noreferrer"&gt;https://doi.org/10.1287/mnsc.1110.1376&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doherty, W. J., &amp;amp; Thadhani, A. J. (1982). &lt;em&gt;The economic value of rapid response time&lt;/em&gt;. IBM. &lt;a href="https://jlelliotton.blogspot.com/p/the-economic-value-of-rapid-response.html" rel="noopener noreferrer"&gt;Reproduced on Jim Elliott's Mainframe Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Myers, B. A. (1985). The importance of percent-done progress indicators for computer-human interfaces. In &lt;em&gt;Proceedings of the SIGCHI Conference on Human Factors in Computing Systems&lt;/em&gt; (pp. 11-17). Association for Computing Machinery. &lt;a href="https://www.cs.cmu.edu/~bam/papers/percentdoneCHI85.pdf" rel="noopener noreferrer"&gt;Author-hosted paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nielsen, J. (1993, January 1). &lt;em&gt;Response times: The 3 important limits&lt;/em&gt;. Nielsen Norman Group. &lt;a href="https://www.nngroup.com/articles/response-times-3-important-limits/" rel="noopener noreferrer"&gt;https://www.nngroup.com/articles/response-times-3-important-limits/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wagner, J., &amp;amp; Pollard, B. (2025, September 2). &lt;em&gt;Interaction to Next Paint (INP)&lt;/em&gt;. web.dev. &lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;https://web.dev/articles/inp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding Success Criterion 4.1.3: Status messages&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Yablonski, J. (n.d.). &lt;em&gt;Doherty Threshold&lt;/em&gt;. Laws of UX. &lt;a href="https://lawsofux.com/doherty-threshold/" rel="noopener noreferrer"&gt;https://lawsofux.com/doherty-threshold/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does every operation need to finish within 400 milliseconds?
&lt;/h3&gt;

&lt;p&gt;No. The Doherty Threshold is useful guidance for examining the rhythm of interaction, not a universal completion deadline. Give prompt, meaningful feedback and design longer work with honest status, usable outcomes, and recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does showing a spinner satisfy the Doherty Threshold?
&lt;/h3&gt;

&lt;p&gt;A spinner may acknowledge activity, but it does not establish progress or completion. It also does not reproduce the complete-response conditions discussed in the historical research. Judge feedback by what it lets the user understand and do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a progress bar useful if its estimate is imperfect?
&lt;/h3&gt;

&lt;p&gt;It can be, when it represents real work and communicates its basis honestly. Processing half the records does not necessarily mean half the time remains. If a total is unknown, use an appropriate phase or activity indicator instead of inventing a percentage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I add a delay to make a result look more valuable?
&lt;/h3&gt;

&lt;p&gt;I would explain the genuine work and make ready results available promptly. Research on operational transparency concerns specific conditions; it is not a general recommendation to delay users. Review steps and undo windows should serve clear user needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is good INP the same as a fast completed task?
&lt;/h3&gt;

&lt;p&gt;No. INP concerns interaction responsiveness through the next paint. A task can have prompt initial feedback and a slow or failed asynchronous outcome. Measure both, with clear definitions for success.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Response Contract?
&lt;/h3&gt;

&lt;p&gt;It is an original design-review heuristic used in this article: acknowledge the action, advance with evidence, confirm the outcome, and recover from uncertainty. It helps teams specify behavior; it is not a scientific score or an accessibility certification.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>dohertythreshold</category>
      <category>perceivedperformance</category>
      <category>interactiondesign</category>
    </item>
    <item>
      <title>The Invisible Work: Designing for Cognitive Load</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:40:55 +0000</pubDate>
      <link>https://dev.to/rverwey/the-invisible-work-designing-for-cognitive-load-1jp6</link>
      <guid>https://dev.to/rverwey/the-invisible-work-designing-for-cognitive-load-1jp6</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-invisible-work-cognitive-load-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cognitive load is the mental effort involved in processing information. In an interface, that includes understanding what is on the screen, relating it to a goal, and keeping enough context available to act.&lt;/p&gt;

&lt;p&gt;Imagine a report-scheduling screen with one dropdown and a Continue button. It looks almost impossible to get wrong. Three steps later, you are asked to confirm the schedule. Which pages did you select? Was that nine in the morning in your time zone or the client's? Did you choose the whole team or one recipient?&lt;/p&gt;

&lt;p&gt;The interface has very little on it. Your head is doing the rest.&lt;/p&gt;

&lt;p&gt;That is the distinction I want to explore in this fifth article in &lt;strong&gt;Applied Design Theory&lt;/strong&gt;. Reducing visible information does not necessarily reduce mental work. Sometimes it simply moves that work somewhere we cannot see in a screenshot.&lt;/p&gt;

&lt;p&gt;As a developer, I find that more useful than treating cognitive load as a synonym for a busy layout. It turns the discussion toward state, dependencies, language, feedback, and recovery: things we can actually design and implement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good interface does not remove every difficult decision. It stops making people carry information the product could reliably carry for them.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quiet Screen Can Still Be a Demanding Screen
&lt;/h2&gt;

&lt;p&gt;We can inspect spacing, count controls, and remove decorative elements. Those are concrete activities, so they are tempting places to begin. But the effort of using software is not contained entirely within the current viewport.&lt;/p&gt;

&lt;p&gt;A person may be translating a label into terminology their organization uses. They may be comparing a monthly price with an annual budget. They may be remembering the selection from an earlier screen while reading an explanation on this one. They may be deciding whether a spinner means the action is still running or has failed.&lt;/p&gt;

&lt;p&gt;None of those demands necessarily creates visible clutter.&lt;/p&gt;

&lt;p&gt;Consider two versions of the same confirmation step. One says, "Ready to finish?" The other names the selected pages, schedule, time zone, and recipients, with a way to edit each choice.&lt;/p&gt;

&lt;p&gt;The second version contains more information. It also gives the person something to verify instead of something to reconstruct. That is a design hypothesis worth testing, not proof that adding summaries always improves usability. A summary can itself be poorly organized, stale, or so exhaustive that the consequential details disappear.&lt;/p&gt;

&lt;p&gt;My starting question is therefore not, "Can we remove something?" It is, &lt;strong&gt;"What work does the person have to do here, and why are they the one doing it?"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Research Actually Gives Us
&lt;/h2&gt;

&lt;p&gt;John Sweller's 1988 paper examined how the demands of conventional problem solving could interfere with learning. A central concern was the processing required to search for a solution versus the development of knowledge structures that support future problems (Sweller, 1988).&lt;/p&gt;

&lt;p&gt;That is an educational research foundation, not an experiment comparing two SaaS dashboards. Applying it to interfaces requires judgment. I use the research to ask better questions about mental demands, not to claim that a particular component is scientifically guaranteed to improve conversion.&lt;/p&gt;

&lt;p&gt;The theory also gives us distinctions that are more useful than simply calling everything overwhelming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intrinsic Load: The Relationships the Task Requires
&lt;/h3&gt;

&lt;p&gt;Intrinsic load concerns the information that must be handled together to understand the material. Its difficulty depends on the interaction between those elements and the person's prior knowledge, not just the number of things displayed (Sweller et al., 2019).&lt;/p&gt;

&lt;p&gt;Scheduling a report has real dependencies. The correct recipients depend on its contents. The meaning of a time depends on a time zone. A first-time user may need those relationships explained; an experienced administrator may already understand them.&lt;/p&gt;

&lt;p&gt;We cannot make those dependencies disappear by hiding their controls. We can sequence the task, clarify the relationships, and avoid asking for decisions before the information needed to make them is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extraneous Load: Work Added by the Presentation
&lt;/h3&gt;

&lt;p&gt;Extraneous load comes from how information or procedures are presented rather than from the learning task itself (Sweller et al., 2019).&lt;/p&gt;

&lt;p&gt;In my interface reviews, I look for analogous avoidable demands: a recipient list on one page and permission information on another; two names for the same setting; an error message that requires hunting for the affected field. Decoration is only one possible source of interference.&lt;/p&gt;

&lt;p&gt;The useful boundary is not "content versus styling." It is the distinction between understanding the decision and working around the way we presented it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Caution About the Three-Bucket Diagram
&lt;/h3&gt;

&lt;p&gt;You may also encounter &lt;em&gt;germane load&lt;/em&gt;. The theory has evolved: the 2019 review discusses germane resources as working-memory resources devoted to dealing with intrinsic load, rather than a separate additive source of load (Sweller et al., 2019).&lt;/p&gt;

&lt;p&gt;I would not turn those terms into three dashboard percentages. They are theoretical distinctions, not quantities we can extract from a screenshot. For product work, naming the specific demand is usually more actionable than arguing over its category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop Treating Memory as Free Storage
&lt;/h2&gt;

&lt;p&gt;Suppose we are designing a hypothetical tool that schedules website reports. This is an illustrative workflow, not a description of a measured product experiment.&lt;/p&gt;

&lt;p&gt;The user chooses &lt;strong&gt;All pages&lt;/strong&gt;, selects &lt;strong&gt;Mondays at 9:00 a.m.&lt;/strong&gt;, and adds the &lt;strong&gt;Product team&lt;/strong&gt; as recipients. A wizard makes each step compact. But if earlier choices disappear, the person must retain them while making later decisions.&lt;/p&gt;

&lt;p&gt;Then something interrupts them. They come back, recognize the form, and still do not know whether their remembered choices match the saved ones.&lt;/p&gt;

&lt;p&gt;A better candidate design retains a concise summary through the flow and presents it clearly before the action becomes live. The summary does not need to repeat every available option. It needs to represent the choices that determine the outcome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqoswj0xx4ct0vm56gf3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqoswj0xx4ct0vm56gf3.webp" alt="Comparison of a report wizard that hides previous choices and a review panel showing All pages, Mondays at 9:00 a.m., America/New_York, and Product team together." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same scheduling decision can require recall or support verification. This is an illustrative comparison, not a measured performance result.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice what changed. We did not remove the time-zone decision. We made its meaning available at the point where the user needs to confirm it. We did not silently choose recipients to save a click. We kept the recipient choice visible.&lt;/p&gt;

&lt;p&gt;This aligns with Whitenton's (2013) practitioner guidance to offload appropriate work onto the interface and avoid unnecessary mental processing. My extension is to treat the summary as part of the task's functional contract, not as optional explanatory decoration.&lt;/p&gt;

&lt;p&gt;If the submitted values differ from the review, we have not reduced cognitive load. We have made the interface easier to trust than it deserves to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Work Ledger
&lt;/h2&gt;

&lt;p&gt;I use the phrase &lt;strong&gt;Mental Work Ledger&lt;/strong&gt; for a practical review method: write down what a person must locate, interpret, hold, compare, and resume at a particular moment.&lt;/p&gt;

&lt;p&gt;This is my own design heuristic, not a validated psychological scale. There are no load points to total and no universal passing score. Its purpose is to make hidden demands specific enough to discuss, implement, and test.&lt;/p&gt;

&lt;p&gt;For each demand, record why it exists, whether it serves the person's goal, what the interface currently provides, and what evidence would tell us the support is working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6uivzmvtlmyj8m8uhwf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6uivzmvtlmyj8m8uhwf.webp" alt="The Mental Work Ledger maps Locate to stable navigation, Interpret to plain language, Hold to a selection summary, Compare to aligned context, and Resume to saved state and a next step." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Five questions for reviewing mental work. The categories organize a conversation; they do not measure a person's capacity.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Locate: Where Is the Next Useful Thing?
&lt;/h3&gt;

&lt;p&gt;In our scheduling example, the person needs to find the current step, the next action, and the way to revisit a choice. If those move unpredictably, every transition becomes another orientation task.&lt;/p&gt;

&lt;p&gt;I would look for stable placement, descriptive headings, and a visible distinction between navigation and commitment. "Continue" moves through setup. "Create schedule" makes something happen. Using the same vague label for both asks the user to infer a difference the product already knows.&lt;/p&gt;

&lt;p&gt;The test is not whether someone notices the button. It is whether they can find the next appropriate action and explain what it will do.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Interpret: What Does This Mean Here?
&lt;/h3&gt;

&lt;p&gt;A label such as "Scope" may be natural to the development team and unclear to someone scheduling their first report. A short explanation, "Choose which pages this report includes," can connect the label to the task.&lt;/p&gt;

&lt;p&gt;The goal is not to eliminate every domain term. Some vocabulary is essential to learning the product. It is to introduce that vocabulary where its meaning is useful, rather than force people to consult another page before they can continue.&lt;/p&gt;

&lt;p&gt;Ask participants to describe the setting in their own words. A correct click can conceal an incorrect interpretation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hold: What Must Stay Available?
&lt;/h3&gt;

&lt;p&gt;The chosen pages, time zone, and recipients influence later decisions. They should not exist only in the user's recollection.&lt;/p&gt;

&lt;p&gt;A persistent summary can help, but it must reflect current state. If an edit changes the audience, the review must update. If a value is invalid, the summary should not confidently present it as settled.&lt;/p&gt;

&lt;p&gt;I test this by changing an earlier selection and by returning after an interruption. Can the person establish the current configuration from the interface, without reconstructing their previous actions?&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Compare: Are We Making People Translate?
&lt;/h3&gt;

&lt;p&gt;If one option uses local time and another uses an unspecified system time, we have introduced a conversion problem. If recipient groups are shown by name but their membership is inaccessible, comparison may require another tool.&lt;/p&gt;

&lt;p&gt;Put comparable information into compatible terms. Show the relevant unit, time zone, or basis. Keep the consequences of a selection close enough to the choice to be consulted without a scavenger hunt.&lt;/p&gt;

&lt;p&gt;Then test the relationship, not just recognition: can the user explain how these options would produce different outcomes?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Resume: What Changed While I Was Away?
&lt;/h3&gt;

&lt;p&gt;Returning to a half-finished task is different from starting it. The person needs to recover both the configuration and the status of the action.&lt;/p&gt;

&lt;p&gt;"Draft saved. Next: choose recipients" answers two useful questions. "Success" answers neither very well. Was a draft saved, a report generated, or a recurring schedule activated?&lt;/p&gt;

&lt;p&gt;A return-state test should establish whether the person can identify what has happened, what remains unfinished, and what action would make the schedule live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk Through the Decision, Not Just the Screens
&lt;/h2&gt;

&lt;p&gt;Here is how I would use the ledger to review our hypothetical workflow before polishing it.&lt;/p&gt;

&lt;p&gt;First, define the outcome precisely: the user creates a recurring report for the intended pages, delivered to the intended people at the intended time. "Completes the form" is not an adequate substitute. A confidently misconfigured report is still a failed task.&lt;/p&gt;

&lt;p&gt;Next, follow the dependencies. The report's contents affect who should receive it. The time-zone interpretation affects when it arrives. The final review needs both. That tells us which decisions belong together and which information should survive transitions.&lt;/p&gt;

&lt;p&gt;Then inspect each change of state. When the user edits the page selection, are dependent settings still valid? When saving fails, are the choices retained? When the user returns, does the interface distinguish the draft from an active schedule?&lt;/p&gt;

&lt;p&gt;Finally, choose a small testable change. For example: preserve the selected scope and time zone beside the recipient step, then repeat them in a final review with edit links. The hypothesis is that people can make and verify recipient decisions without navigating backward to recover context.&lt;/p&gt;

&lt;p&gt;We would observe whether that happens. We would also watch for a competing problem: perhaps the persistent summary dominates the screen and makes the current task harder to locate. The ledger is not permission to add every helpful-sounding component at once.&lt;/p&gt;

&lt;p&gt;This approach changes the design conversation. Instead of defending a layout as "cleaner," we can explain which demand it addresses and how we will know whether it helped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive Disclosure Can Move the Problem
&lt;/h2&gt;

&lt;p&gt;Progressive disclosure is useful when it postpones information that is genuinely irrelevant to the current decision. It is less useful when it conceals something the person needs in order to make that decision responsibly.&lt;/p&gt;

&lt;p&gt;In the scheduling example, an advanced formatting option can probably wait. The recipients and effective time zone should not be hidden behind an accordion on the final confirmation screen simply because the summary looks neater without them.&lt;/p&gt;

&lt;p&gt;I separate &lt;strong&gt;optional configuration&lt;/strong&gt; from &lt;strong&gt;decision context&lt;/strong&gt;. Optional configuration can remain available on demand. Decision context needs to be available when the consequence is being evaluated.&lt;/p&gt;

&lt;p&gt;The same distinction matters on mobile. Moving the summary to a desktop sidebar does not solve the problem if that sidebar disappears at a narrow breakpoint. A compact inline summary or expandable section with meaningful current values can preserve the relationship without recreating the desktop layout.&lt;/p&gt;

&lt;p&gt;This connects to &lt;a href="https://www.ryanverwey.dev/blog/the-shape-of-understanding-chunking-ui" rel="noopener noreferrer"&gt;The Shape of Understanding&lt;/a&gt;: grouping should expose relationships, not merely put borders around content. A stack of collapsed groups may look organized while still requiring the user to remember what each contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helpful for Whom, and at Which Moment?
&lt;/h2&gt;

&lt;p&gt;The expertise reversal effect describes how instructional techniques that help less experienced learners can become less useful, or counterproductive, as expertise increases (Kalyuga et al., 2003). That research concerns instruction; I treat it as a reason to investigate audience differences, not a rule that all expert users want fewer explanations.&lt;/p&gt;

&lt;p&gt;Expertise is also specific. Someone may know React extremely well and still be new to scheduling reports across client time zones. A job title is not a reliable substitute for understanding the task.&lt;/p&gt;

&lt;p&gt;For our hypothetical product, I would make basic explanations easy to consult and avoid forcing experienced users through the same introductory material repeatedly. But I would keep consequential state visible for everyone. Knowing the product does not make a stale recipient list safe.&lt;/p&gt;

&lt;p&gt;A first-time setup flow might explain the relationship between schedule time and time zone. A repeat visit might present the current setting compactly. Both should allow inspection and correction.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://www.ryanverwey.dev/blog/the-novelty-budget-familiar-vs-novel-ui" rel="noopener noreferrer"&gt;The Novelty Budget&lt;/a&gt; remains relevant. Familiar conventions can spare people avoidable interpretation, but familiarity with one convention does not guarantee familiarity with the domain behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for Interruption Without Promising Too Much
&lt;/h2&gt;

&lt;p&gt;We often review a flow as though someone will complete it uninterrupted, in the intended order, with a stable connection. That is a useful first path to verify. It is an incomplete product model.&lt;/p&gt;

&lt;p&gt;For the scheduling tool, I would define separate draft and active states. Saving a draft preserves authorized information without creating the schedule. Returning to it shows what is preserved and what remains incomplete. Activation is a separate, explicit action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbeookulsi94jetnrblco.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbeookulsi94jetnrblco.webp" alt="An interrupted scheduling workflow moves from unfinished choices to a saved draft and then a return screen with the next step, clearly distinguishing saved state from a submitted schedule." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A schematic return journey. Preserving inputs is only part of recovery; the interface must also explain the action's status.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That convenience carries implementation responsibilities. Drafts need an appropriate storage lifetime, access controls, and behavior when permissions change. Shared devices and sensitive recipient data may make some persistence strategies inappropriate. "Remember everything" is not a responsible default.&lt;/p&gt;

&lt;p&gt;Within a process, the accessibility guidance is more specific. WCAG 2.2's Redundant Entry criterion addresses information that would otherwise have to be supplied again in the same process, with exceptions including security, essential re-entry, and invalid previous information. It does not require retaining information between sessions (World Wide Web Consortium, 2025).&lt;/p&gt;

&lt;p&gt;So I separate two decisions: avoiding unnecessary repeated entry during the task, and offering safe cross-session recovery. Both can support users. They are not the same requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frontend Contract Behind the Calm Interface
&lt;/h2&gt;

&lt;p&gt;A reassuring summary is easy to draw. Keeping it accurate across edits, failures, and delayed responses takes engineering work.&lt;/p&gt;

&lt;p&gt;For a workflow like this, my implementation review would include these checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use one authoritative configuration.&lt;/strong&gt; The review and submission payload should derive from the same current values, rather than separately maintained copies that can drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Represent status explicitly.&lt;/strong&gt; Draft, saving, save failed, ready to submit, and active are different states. The UI should not collapse them into a generic success indicator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve valid input through errors.&lt;/strong&gt; A failed save should not erase unrelated choices. Explain what failed and what the user can do next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make dependencies visible.&lt;/strong&gt; If changing scope invalidates another selection, explain the consequence instead of silently replacing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep time semantics intact.&lt;/strong&gt; Store and display the information needed to interpret the schedule, including its time zone, rather than relying on an ambiguous clock label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make feedback perceivable.&lt;/strong&gt; Associate errors with their fields, maintain a sensible focus order, and provide accessible status feedback. A visual summary alone is insufficient.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are implementation checks, not a claim that the interface automatically satisfies an accessibility standard. They make the proposed support dependable enough to evaluate.&lt;/p&gt;

&lt;p&gt;The same scrutiny applies to AI interfaces. One prompt box can look simpler than a form while asking the user to describe the task, infer what the system understood, and inspect the result for missing constraints. A generated schedule still needs reviewable values and an explicit commitment step.&lt;/p&gt;

&lt;p&gt;Changing the input method does not eliminate the responsibility to show what will happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Understanding, Not Just Motion
&lt;/h2&gt;

&lt;p&gt;We cannot infer cognitive load from a control count, a screenshot, or one completion-time number.&lt;/p&gt;

&lt;p&gt;Paas (1992) investigated subjective mental-effort ratings alongside performance in a study of learning and transfer in statistics. That work supports taking perceived effort seriously as evidence; it does not give us a universal product score or a direct reading of someone's remaining mental capacity.&lt;/p&gt;

&lt;p&gt;For our workflow, I would combine several observations: whether the final configuration is correct, whether the person can explain it, where they backtrack or seek help, whether they can recover after an interruption, and how effortful they report the task to be.&lt;/p&gt;

&lt;p&gt;Keep task conditions and participant experience in view. Comparing a novice handling a complex report with an expert handling a routine one does not isolate the interface's contribution. A useful comparison needs equivalent goals and attention to relevant experience.&lt;/p&gt;

&lt;p&gt;I would also resist declaring victory because the new version is faster. Perhaps it supports understanding. Perhaps it encourages people to skip the recipients and accept a default. A brief explanation of the intended outcome can help distinguish those possibilities.&lt;/p&gt;

&lt;p&gt;This is the same discipline discussed in &lt;a href="https://www.ryanverwey.dev/blog/the-confidence-trap-cognitive-bias-ui" rel="noopener noreferrer"&gt;The Confidence Trap&lt;/a&gt;: an observable result and our preferred explanation are not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Effort That Protects the Decision
&lt;/h2&gt;

&lt;p&gt;Some effort belongs in the experience. Reviewing who will receive a report is not a nuisance merely because it slows completion. Understanding a permission change is not wasted time.&lt;/p&gt;

&lt;p&gt;I want to remove the effort of finding scattered facts, not the opportunity to consider their consequences. I want to reduce avoidable recall, not replace consent with an invisible assumption.&lt;/p&gt;

&lt;p&gt;That distinction also connects to &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;The Decision Tax&lt;/a&gt;. Fewer choices can help in some circumstances, but narrowing the visible set is not enough if users can no longer establish whether the remaining options meet their needs.&lt;/p&gt;

&lt;p&gt;The practical aim is a better division of labor: the system retains reliable state and presents relevant relationships; the person evaluates goals, tradeoffs, and consequences. Neither side should be asked to do the other's job by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The next time an interface feels demanding, resist beginning and ending with the amount of content on the screen.&lt;/p&gt;

&lt;p&gt;Follow the task. Find the moment someone has to remember a vanished choice, translate an ambiguous label, reconcile incompatible units, or guess whether an action succeeded. Name that demand. Decide whether it contributes to the goal. Then provide support and test whether it actually helps.&lt;/p&gt;

&lt;p&gt;A visually simple interface can still leave a person doing substantial invisible work. A more informative interface can make the same decision easier to understand and verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal is not an empty screen. It is a person who can tell what they are doing, why it matters, and what will happen next.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Kalyuga, S., Ayres, P., Chandler, P., &amp;amp; Sweller, J. (2003). The expertise reversal effect. &lt;em&gt;Educational Psychologist, 38&lt;/em&gt;(1), 23-31. &lt;a href="https://doi.org/10.1207/S15326985EP3801_4" rel="noopener noreferrer"&gt;https://doi.org/10.1207/S15326985EP3801_4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paas, F. G. W. C. (1992). Training strategies for attaining transfer of problem-solving skill in statistics: A cognitive-load approach. &lt;em&gt;Journal of Educational Psychology, 84&lt;/em&gt;(4), 429-434. &lt;a href="https://doi.org/10.1037/0022-0663.84.4.429" rel="noopener noreferrer"&gt;https://doi.org/10.1037/0022-0663.84.4.429&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sweller, J. (1988). Cognitive load during problem solving: Effects on learning. &lt;em&gt;Cognitive Science, 12&lt;/em&gt;(2), 257-285. &lt;a href="https://doi.org/10.1207/s15516709cog1202_4" rel="noopener noreferrer"&gt;https://doi.org/10.1207/s15516709cog1202_4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sweller, J., van Merriënboer, J. J. G., &amp;amp; Paas, F. (2019). Cognitive architecture and instructional design: 20 years later. &lt;em&gt;Educational Psychology Review, 31&lt;/em&gt;, 261-292. &lt;a href="https://doi.org/10.1007/s10648-019-09465-5" rel="noopener noreferrer"&gt;https://doi.org/10.1007/s10648-019-09465-5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whitenton, K. (2013, December 22). &lt;em&gt;Minimize cognitive load to maximize usability&lt;/em&gt;. Nielsen Norman Group. &lt;a href="https://www.nngroup.com/articles/minimize-cognitive-load/" rel="noopener noreferrer"&gt;https://www.nngroup.com/articles/minimize-cognitive-load/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (2025, September 17). &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html" rel="noopener noreferrer"&gt;&lt;em&gt;Understanding Success Criterion 3.3.7: Redundant entry&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is cognitive load in UI/UX design?
&lt;/h3&gt;

&lt;p&gt;Cognitive load concerns the mental effort required to process information. In interface use, relevant demands include understanding labels, relating information to a goal, remembering earlier choices, and evaluating what an action will do. Visible complexity is only part of that experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does reducing cognitive load mean showing fewer options?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. Removing irrelevant options may help, but hiding essential context can increase recall and navigation demands. Decide what information the current decision requires before deciding how little to display.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Miller's Law mean a screen should have seven items?
&lt;/h3&gt;

&lt;p&gt;No. A memory-capacity discussion is not a universal limit for menus, fields, or dashboard components. The task, relationships between information, presentation, and user's knowledge matter. Our &lt;a href="https://www.ryanverwey.dev/blog/the-shape-of-understanding-chunking-ui" rel="noopener noreferrer"&gt;chunking article&lt;/a&gt; explores why meaningful grouping is more useful than an arbitrary item count.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can a team evaluate whether a redesign reduced unnecessary effort?
&lt;/h3&gt;

&lt;p&gt;Use representative tasks and combine correct outcomes, comprehension, observed difficulties, recovery behavior, and self-reported effort. Compare equivalent conditions and account for experience. Faster completion alone does not establish better understanding or lower cognitive load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the Mental Work Ledger a scientific assessment?
&lt;/h3&gt;

&lt;p&gt;No. It is an original review heuristic for identifying demands and proposing testable support. It does not diagnose users, calculate memory capacity, or replace research with the people who will use the product.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>cognitiveload</category>
      <category>interactiondesign</category>
      <category>informationarchitecture</category>
    </item>
    <item>
      <title>The Confidence Trap: Cognitive Bias on Both Sides of the Screen</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:38:43 +0000</pubDate>
      <link>https://dev.to/rverwey/the-confidence-trap-cognitive-bias-on-both-sides-of-the-screen-4oc5</link>
      <guid>https://dev.to/rverwey/the-confidence-trap-cognitive-bias-on-both-sides-of-the-screen-4oc5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-confidence-trap-cognitive-bias-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cognitive bias is a systematic tendency in judgment that can pull an interpretation away from the evidence. In interface design, it matters twice: when someone makes a choice using our product, and when we decide what their behavior means.&lt;/p&gt;

&lt;p&gt;Imagine adding a recommendation badge to a pricing page. More visitors choose the highlighted plan. The team celebrates a clearer interface.&lt;/p&gt;

&lt;p&gt;Maybe that is what happened. Maybe the badge simply drew attention. Maybe visitors misunderstood the billing interval. Maybe a different campaign brought in a different audience that week.&lt;/p&gt;

&lt;p&gt;The event is real. The explanation is still unfinished.&lt;/p&gt;

&lt;p&gt;That gap is the subject of this fourth article in &lt;strong&gt;Applied Design Theory&lt;/strong&gt;. I am less interested in memorizing a hundred bias names than in the moment a plausible story becomes an unquestioned product decision. As a developer, I want something I can use in a design review, a pull request, or a conversation about whether a feature is actually helping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A more convincing interface is not necessarily a more understandable one. A more confident team is not necessarily a better-informed one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Shortcut Is Not Automatically a Mistake
&lt;/h2&gt;

&lt;p&gt;A heuristic is a practical shortcut for making a judgment. A bias is a systematic pattern of error, not simply another word for speed, emotion, or a preference the designer dislikes.&lt;/p&gt;

&lt;p&gt;Tversky and Kahneman (1974) examined how people estimate uncertain outcomes using heuristics such as availability, representativeness, and anchoring. Their work showed how useful simplifying strategies can also produce predictable errors. The important distinction is between the strategy and the circumstances in which it misleads. &lt;a href="https://doi.org/10.1126/science.185.4157.1124" rel="noopener noreferrer"&gt;Read the original paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recognizing a familiar navigation pattern is not a defect in reasoning. Neither is choosing the tool your team already knows when migration would be expensive. A supposedly inferior choice can make sense once time, risk, constraints, and personal priorities are considered.&lt;/p&gt;

&lt;p&gt;For that reason, I would not call a user biased because they ignored the option I wanted them to choose. First, I need to understand their goal. Then I need to understand what they believed the options would do.&lt;/p&gt;

&lt;p&gt;The design problem begins when the interface encourages an inference it cannot support: that a prominently displayed plan is suitable for everyone, that a polished score is a complete assessment, or that leaving a setting unchanged means someone deliberately agreed with it.&lt;/p&gt;

&lt;p&gt;My responsibility is not to eliminate shortcuts. It is to make the shortcuts the product invites reasonably trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Sides of the Screen
&lt;/h2&gt;

&lt;p&gt;I find it useful to separate two questions during a review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the visitor's side: what conclusion does this presentation invite?&lt;/strong&gt; A price, badge, default, testimonial, or warning does more than occupy space. It helps establish what looks normal, urgent, credible, or worth comparing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the team's side: what conclusion are we drawing from the response?&lt;/strong&gt; A click, completion, complaint, or interview quote is evidence of something. It is not automatically evidence of the thing we hoped to prove.&lt;/p&gt;

&lt;p&gt;These questions belong together because a team can create a persuasive presentation, observe the behavior it encouraged, and then treat that behavior as independent confirmation that its original idea was correct.&lt;/p&gt;

&lt;p&gt;Consider a hypothetical report tool. The product describes one issue as urgent, places it first, and gives it the strongest color. Customers open that issue most often. Calling it their highest priority skips over the role the interface played in directing attention.&lt;/p&gt;

&lt;p&gt;It may genuinely deserve first place. But that judgment needs evidence about consequences and user goals, not just evidence that people followed the hierarchy we built.&lt;/p&gt;

&lt;p&gt;This is the confidence trap: &lt;strong&gt;we design the conditions of a decision, then forget those conditions when interpreting the result.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Number Can Stay the Same While the Story Changes
&lt;/h2&gt;

&lt;p&gt;Framing concerns how a decision is presented. Tversky and Kahneman (1981) demonstrated shifts in preference when equivalent decision problems were expressed differently. That finding supports examining presentation; it does not tell us that every positive phrase will outperform every negative one in every product. &lt;a href="https://doi.org/10.1126/science.7455683" rel="noopener noreferrer"&gt;Read the study&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a particularly relevant example for our own profession. Whitenton (2017) reported an online investigation involving more than 1,000 UX practitioners who received differently framed versions of a hypothetical search-usability result. Support for redesign was 51% with failure wording and 39% with success wording. This was a difference in practitioners' judgments about a scenario, not a measured improvement in a live website. &lt;a href="https://www.nngroup.com/articles/decision-framing-cognitive-bias-ux-pros/" rel="noopener noreferrer"&gt;Read the investigation and its context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a separate, invented example for our review table. In one onboarding test, 40 of 50 invited participants create a first project. Saying that 80% completed and saying that 20% did not are both accurate. Neither tells us whether to launch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkeox9r5vqmvjnkihgvcr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkeox9r5vqmvjnkihgvcr.webp" alt="A hypothetical onboarding result shown as 80 percent completion and 20 percent noncompletion, with the sample, task, test period, and missing failure reasons kept visible." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These numbers are illustrative, not research results. The graphic demonstrates equivalent descriptions and the context still needed to interpret them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I would want to know who participated, where the failures happened, whether assistance counted as completion, and what the task represented. Failing to create a disposable demo project and failing to submit a time-sensitive application are not interchangeable outcomes.&lt;/p&gt;

&lt;p&gt;My practical rule for a report is to keep the underlying count, population, period, and definition available next to the headline. Where useful, show both completed and incomplete outcomes. This will not make a decision neutral, but it makes a one-sided summary easier to question.&lt;/p&gt;

&lt;p&gt;The same discipline belongs in product copy. A monthly equivalent should not conceal an annual charge. A percentage improvement should identify its baseline. A recommendation should explain its criteria. Readers should not have to reverse-engineer the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirmation Bias Can Look Like Thorough Research
&lt;/h2&gt;

&lt;p&gt;Nickerson (1998) describes confirmation bias as favoring existing beliefs in the search for or interpretation of evidence. His review covers more than simply refusing to listen: the questions asked and the weight assigned to information can also favor an expected conclusion. &lt;a href="https://doi.org/10.1037/1089-2680.2.2.175" rel="noopener noreferrer"&gt;Read Nickerson's review&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For product work, my concern is research that begins with a verdict and ends with supporting material.&lt;/p&gt;

&lt;p&gt;Suppose I want to replace a settings page with a conversational assistant. I could gather enthusiastic comments about AI, demonstrate the easiest task, and ask whether the new interaction feels modern. Every step produces material, but none directly tests whether the assistant is a better way to manage settings.&lt;/p&gt;

&lt;p&gt;A more useful question would be: can people make and verify the intended change without introducing another one? That question leaves room for the existing page to perform better. It also allows the assistant to succeed for some tasks and fail for others.&lt;/p&gt;

&lt;p&gt;Before building a prototype, I would write down the proposed advantage and a credible reason it might not appear. For the assistant, a plausible failure is that conversational convenience makes the final configuration harder to inspect.&lt;/p&gt;

&lt;p&gt;That changes the prototype. It now needs a visible summary, a clear commit step, and a way to review what changed. The competing explanation has produced a better engineering question before a study has even begun.&lt;/p&gt;

&lt;p&gt;The objective is not to manufacture disagreement. It is to make the preferred explanation answerable to something other than our enthusiasm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record the Behavior Before Telling Its Story
&lt;/h2&gt;

&lt;p&gt;For ambiguous findings, I use a small evidence ledger: observation, first explanation, alternatives, and next test. It is a working documentation pattern, not a diagnostic instrument.&lt;/p&gt;

&lt;p&gt;Return to the hypothetical pricing page. The observation is that more visitors selected the highlighted plan. The interpretation is that the recommendation helped them choose. Those belong on different lines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvso7scz5bs335m34x97.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvso7scz5bs335m34x97.webp" alt="An evidence ledger separates increased selection of a highlighted plan from an unproven explanation, lists alternative explanations, and proposes checking plan fit and billing comprehension." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An original worked example. The ledger preserves uncertainty rather than assigning a psychological cause to a click.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next test should distinguish explanations, not merely collect more of the same signal. If the claim is better plan selection, ask whether the chosen plan supports the person's stated requirements. If the claim is clearer pricing, ask them to explain what they expect to pay and when.&lt;/p&gt;

&lt;p&gt;Even an experiment showing that the badge caused more selections would not, by itself, establish why it worked or whether customers benefited. A causal effect on a click is narrower than a causal explanation of understanding.&lt;/p&gt;

&lt;p&gt;I also want the ledger to retain contrary observations. Someone choosing the highlighted plan and then immediately switching away is not an inconvenient detail to remove from a success story. It may help explain the story.&lt;/p&gt;

&lt;p&gt;There is an engineering benefit here: a future maintainer can see which decisions were supported, which were provisional, and what evidence would justify revisiting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Anchor Is Not the Same as a Useful Reference
&lt;/h2&gt;

&lt;p&gt;Anchoring describes judgments being pulled toward an initial reference, with adjustment that can be insufficient. Availability concerns judgments influenced by how readily examples come to mind. Both appear in Tversky and Kahneman's (1974) discussion of judgment under uncertainty.&lt;/p&gt;

&lt;p&gt;I would not turn either concept into an automatic prescription to remove references or ignore memorable incidents.&lt;/p&gt;

&lt;p&gt;A pricing comparison needs reference points. The question is whether the reference helps someone assess their actual decision. Showing a legitimate annual total beside a monthly equivalent can clarify a commitment. Displaying an unrelated high price only to make another number feel small does not provide the same service.&lt;/p&gt;

&lt;p&gt;Likewise, a vivid support complaint can reveal a serious problem before it appears frequently in a dashboard. One report of a destructive workflow may deserve immediate investigation. Its seriousness and its prevalence are separate questions.&lt;/p&gt;

&lt;p&gt;For a roadmap discussion, I would record both: how many people we know are affected, and how consequential the failure is. I would also record what we do not know. A small count can reflect a rare problem, weak reporting, or a problem that prevents people from reaching support.&lt;/p&gt;

&lt;p&gt;The goal is not to replace a striking story with a reassuring average. It is to stop either from pretending to be the whole picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The People Missing From the Data Matter
&lt;/h2&gt;

&lt;p&gt;Not every misleading conclusion is best understood as a cognitive bias. A sample that excludes important users is also a research-design problem. Broken event tracking is an instrumentation problem. Missing authorization feedback may be a product defect.&lt;/p&gt;

&lt;p&gt;Calling all of these things bias can make a discussion sound sophisticated while obscuring what needs fixing.&lt;/p&gt;

&lt;p&gt;Imagine evaluating onboarding only through a survey shown after successful setup. The survey can describe the experience of respondents who got that far. It cannot, on its own, explain why other people never finished.&lt;/p&gt;

&lt;p&gt;For a review of that flow, I would map the evidence to the journey: who saw the entry point, who started, who reached each state, who stopped, and who was invited to provide feedback. The exact measurement method depends on consent, privacy, and the product, but the scope of each claim should remain explicit.&lt;/p&gt;

&lt;p&gt;This also matters when an interface works well for the people in the room. A mouse-based walkthrough does not establish that the workflow is usable from a keyboard. A desktop screenshot does not establish that its explanation survives on a narrow screen.&lt;/p&gt;

&lt;p&gt;Those are observable questions. Test them directly instead of interpreting absent complaints as proof of inclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Awareness Needs Something to Hold It in Place
&lt;/h2&gt;

&lt;p&gt;There is a tempting conclusion after learning about bias: now that I can recognize it, I will make better decisions. I would rather turn that intention into a repeatable practice than rely on remembering it at the right moment.&lt;/p&gt;

&lt;p&gt;That is not an argument that improvement is impossible. Morewedge et al. (2015) reported two experiments in which a video or interactive game reduced selected decision biases, with effects assessed immediately and at follow-up eight or twelve weeks later. The interventions included instruction and strategies, with practice and personalized feedback in the games. The findings concern particular measures and training conditions, not permanent protection against every biased decision. &lt;a href="https://doi.org/10.1177/2372732215600886" rel="noopener noreferrer"&gt;Read the study&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The implication I draw for product work is modest: make room to practice better reasoning and get feedback on it. Do not treat a vocabulary lesson as a completed safeguard.&lt;/p&gt;

&lt;p&gt;For a consequential release, I would capture expectations before looking at the result. Ask reviewers to write their initial assessment before a group discussion. Assign someone to investigate a credible alternative, not to oppose the project for sport. Schedule a review after enough evidence can reasonably accumulate.&lt;/p&gt;

&lt;p&gt;These are process choices, not guarantees. They create a record that is harder to quietly rewrite when a favorite feature underperforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counterweight Review
&lt;/h2&gt;

&lt;p&gt;Here is my practical framework for this article: &lt;strong&gt;the Counterweight Review&lt;/strong&gt;. It is an original design-review heuristic, not a validated psychological assessment or a score of how biased a team is.&lt;/p&gt;

&lt;p&gt;The idea is to pair each confident claim with something that could qualify it, challenge it, or tell us when to reconsider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcrxgjfi6wogbj66x4oqm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcrxgjfi6wogbj66x4oqm.webp" alt="The Counterweight Review asks what the claim is, what else could explain it, how the choice is framed, who or what is missing, and what would change the decision." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Five questions for a review document. The purpose is an inspectable decision, not a numerical bias rating.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What Is the Claim?
&lt;/h3&gt;

&lt;p&gt;Write a sentence precise enough to investigate. "The new pricing page is better" is a preference disguised as a finding. "People can identify the least expensive plan that meets their requirements" describes a task.&lt;/p&gt;

&lt;p&gt;For our hypothetical recommendation badge, I would distinguish the business objective, increased adoption, from the user objective, selecting an appropriate plan. They can align, but I do not want to assume that alignment in the measurement.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What Else Could Explain It?
&lt;/h3&gt;

&lt;p&gt;Name at least one plausible alternative before declaring success. Changed traffic, visual prominence, clearer copy, and mistaken assumptions about price are different explanations with different implications.&lt;/p&gt;

&lt;p&gt;Choose alternatives that could change what you build. A list of remote possibilities is not useful if nobody can investigate it. Here, checking expected billing and actual requirements could reveal whether increased selection reflects understanding or confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. How Is the Choice Framed?
&lt;/h3&gt;

&lt;p&gt;Inspect comparisons, defaults, labels, order, and omitted context. What is being made easy to notice? What must someone open another page to discover?&lt;/p&gt;

&lt;p&gt;For the badge, ask what "Recommended" means. If it is based on a stated team size and required features, show that basis. If there is no individual fit assessment, do not present the label as though one occurred. A general description such as "Includes team permissions" may be more defensible.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Who or What Is Missing?
&lt;/h3&gt;

&lt;p&gt;Identify the limits of the evidence. Are we hearing only from paid customers, successful completers, or people willing to answer a survey? Did the test exclude a device or interaction method central to real use?&lt;/p&gt;

&lt;p&gt;For the pricing example, include people who decided that none of the plans fit. Choosing not to buy can be a successful decision for the visitor. It should not automatically be classified as a design failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What Would Change the Decision?
&lt;/h3&gt;

&lt;p&gt;Specify a test and a condition for reconsideration. If more people select a plan but cannot explain the annual commitment, I would not call the result a usability improvement.&lt;/p&gt;

&lt;p&gt;Record who owns the follow-up and what evidence is still needed. A decision can be provisional without being directionless. "Ship to a limited audience while checking comprehension and cancellation reasons" is different from "Ship, then look for good numbers."&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the Review Into an Implementation Contract
&lt;/h2&gt;

&lt;p&gt;The most useful outcome of a design discussion is often a small change to what the software must represent.&lt;/p&gt;

&lt;p&gt;For the hypothetical pricing page, I would want the data model to distinguish a charge amount from its display equivalent. The UI should not reconstruct an annual commitment from a marketing string. A recommendation should have an explicit basis that can be displayed, tested, and changed without rewriting unrelated content.&lt;/p&gt;

&lt;p&gt;That produces concrete acceptance criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The billing interval and amount charged appear together before confirmation.&lt;/li&gt;
&lt;li&gt;A recommendation includes a truthful explanation of its basis.&lt;/li&gt;
&lt;li&gt;Comparable features use consistent names and units across plans.&lt;/li&gt;
&lt;li&gt;Selecting another plan does not require dismissing a discouraging or misleading message.&lt;/li&gt;
&lt;li&gt;The final confirmation identifies the selected plan and commitment.&lt;/li&gt;
&lt;li&gt;The same decision-critical information remains available on mobile and through keyboard navigation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not a request to turn every screen into a disclaimer. It is a request to keep the facts that justify a choice connected to the choice itself.&lt;/p&gt;

&lt;p&gt;Testing can then cover more than rendering. Does switching billing modes update both the price and the commitment? Does the recommendation explanation match the eligibility rule? Can a person review a selection before it becomes an action?&lt;/p&gt;

&lt;p&gt;Psychology has become useful here because it led to better requirements, not because a bias name was attached to a component.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Summaries Need the Same Scrutiny
&lt;/h2&gt;

&lt;p&gt;A generated summary can compress research notes into a fluent explanation. I would still treat that explanation as an interpretation requiring inspection.&lt;/p&gt;

&lt;p&gt;Suppose a summary says, "Users prefer the simplified dashboard." I would ask which observations support that sentence, which participants it refers to, what simplified means, and whether conflicting evidence was preserved. A polished paragraph should not get a lower burden of proof than a colleague's opinion.&lt;/p&gt;

&lt;p&gt;For an AI-assisted research workflow, I would separate source excerpts, synthesis, and proposed action. Each should be identifiable. A recommendation should link back to the relevant evidence, while uncertainty and missing context remain visible.&lt;/p&gt;

&lt;p&gt;I would also avoid asking only for reasons a proposed redesign will work. Ask what the available evidence cannot establish. Then check the answer against the underlying material. A generated alternative is a prompt for investigation, not additional user research.&lt;/p&gt;

&lt;p&gt;The principle is the same whether the confident sentence came from a model, a dashboard, or me: show the route from observation to conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the Decision, Not Just the Movement
&lt;/h2&gt;

&lt;p&gt;For the pricing example, I would look at three kinds of evidence together: whether people completed the task, whether they understood the commitment, and what happened afterward.&lt;/p&gt;

&lt;p&gt;Completion might include selecting an appropriate plan or correctly deciding that none fits. Understanding might be assessed by asking someone to explain the cost and expected capabilities in their own words. Follow-up might include avoidable plan changes, billing confusion, or support requests, interpreted with their own limitations.&lt;/p&gt;

&lt;p&gt;None of those measures is perfect. Together they make a narrower success story harder to confuse with a complete one.&lt;/p&gt;

&lt;p&gt;Choose the outcome definitions before inspecting the results. When traffic is too limited for a meaningful experiment, do not manufacture certainty from a noisy before-and-after chart. Use task-based observation to investigate misunderstandings and describe what the evidence does and does not support.&lt;/p&gt;

&lt;p&gt;A useful review can end with "We found a comprehension problem and have a specific change to test." It does not need to end with a percentage improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Connects to the Series
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://www.ryanverwey.dev/blog/the-novelty-budget-familiar-vs-novel-ui" rel="noopener noreferrer"&gt;The Novelty Budget&lt;/a&gt;, I argued for spending unfamiliarity where it creates a real benefit. Cognitive bias adds a check on how we decide that benefit exists.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;The Decision Tax&lt;/a&gt;, the focus was helping people narrow and compare options. Here, the additional question is whether the narrowing serves their goals or merely our preferred outcome.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.ryanverwey.dev/blog/the-shape-of-understanding-chunking-ui" rel="noopener noreferrer"&gt;The Shape of Understanding&lt;/a&gt;, grouping information was a responsibility to preserve meaning. Framing asks whether that meaning remains honest when we choose the headline, the comparison, and the details that stay visible.&lt;/p&gt;

&lt;p&gt;These are not separate tricks. They are related ways of examining the relationship between a presentation and the decision it supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Understanding cognitive bias should make a design conversation more curious, not more accusatory. "You are biased" rarely tells a team what to build next. "What else could explain this result?" can.&lt;/p&gt;

&lt;p&gt;I want interfaces that let people understand the basis of a recommendation, compare relevant facts, and change their minds. I want the same qualities in the process used to create those interfaces.&lt;/p&gt;

&lt;p&gt;We will still need judgment. We will still work with incomplete information. The improvement is not perfect objectivity; it is a decision whose assumptions are visible enough to challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence should follow the evidence, and the evidence should remain open to inspection.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Morewedge, C. K., Yoon, H., Scopelliti, I., Symborski, C. W., Korris, J. H., &amp;amp; Kassam, K. S. (2015). Debiasing decisions: Improved decision making with a single training intervention. &lt;em&gt;Policy Insights from the Behavioral and Brain Sciences, 2&lt;/em&gt;(1), 129-140. &lt;a href="https://doi.org/10.1177/2372732215600886" rel="noopener noreferrer"&gt;https://doi.org/10.1177/2372732215600886&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nickerson, R. S. (1998). Confirmation bias: A ubiquitous phenomenon in many guises. &lt;em&gt;Review of General Psychology, 2&lt;/em&gt;(2), 175-220. &lt;a href="https://doi.org/10.1037/1089-2680.2.2.175" rel="noopener noreferrer"&gt;https://doi.org/10.1037/1089-2680.2.2.175&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tversky, A., &amp;amp; Kahneman, D. (1974). Judgment under uncertainty: Heuristics and biases. &lt;em&gt;Science, 185&lt;/em&gt;(4157), 1124-1131. &lt;a href="https://doi.org/10.1126/science.185.4157.1124" rel="noopener noreferrer"&gt;https://doi.org/10.1126/science.185.4157.1124&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tversky, A., &amp;amp; Kahneman, D. (1981). The framing of decisions and the psychology of choice. &lt;em&gt;Science, 211&lt;/em&gt;(4481), 453-458. &lt;a href="https://doi.org/10.1126/science.7455683" rel="noopener noreferrer"&gt;https://doi.org/10.1126/science.7455683&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whitenton, K. (2017, December 19). &lt;em&gt;Decision frames: How cognitive biases affect UX practitioners&lt;/em&gt;. Nielsen Norman Group. &lt;a href="https://www.nngroup.com/articles/decision-framing-cognitive-bias-ux-pros/" rel="noopener noreferrer"&gt;https://www.nngroup.com/articles/decision-framing-cognitive-bias-ux-pros/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is Cognitive Bias in UI/UX Design?
&lt;/h3&gt;

&lt;p&gt;Cognitive bias concerns systematic tendencies in judgment. In UI/UX work, the relevant questions include how an interface presents a decision and how a team interprets the behavior that follows. A click alone does not establish a psychological cause.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Every Mental Shortcut a Cognitive Bias?
&lt;/h3&gt;

&lt;p&gt;No. A heuristic is a simplifying strategy; bias describes a systematic error. Familiar patterns and useful reference points can support good decisions. Whether a shortcut misleads depends on the task, information, and context.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Can a Team Reduce Confirmation Bias?
&lt;/h3&gt;

&lt;p&gt;Separate observations from interpretations, write credible competing explanations, and decide what evidence would change the proposal before judging the result. These practices make assumptions testable; they do not guarantee immunity to bias.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should Designers Stop Using Defaults and Recommendations?
&lt;/h3&gt;

&lt;p&gt;No. Review whether they support a person's goals, explain their basis honestly, keep alternatives understandable, and allow changes. A recommendation should not imply an individualized assessment that the product has not performed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is the Counterweight Review?
&lt;/h3&gt;

&lt;p&gt;It is the original five-question review heuristic introduced in this article: identify the claim, consider alternatives, inspect the frame, identify missing evidence, and specify what would change the decision. It is not a validated bias scale.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>cognitivebias</category>
      <category>userresearch</category>
      <category>interactiondesign</category>
    </item>
    <item>
      <title>The Shape of Understanding: Why Chunking Is More Than Breaking Things Up</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:36:01 +0000</pubDate>
      <link>https://dev.to/rverwey/the-shape-of-understanding-why-chunking-is-more-than-breaking-things-up-3n56</link>
      <guid>https://dev.to/rverwey/the-shape-of-understanding-why-chunking-is-more-than-breaking-things-up-3n56</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-shape-of-understanding-chunking-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Chunking means organizing individual pieces of information into meaningful units. In an interface, the useful part is not simply making those units smaller. It is helping someone understand what belongs together, what it means, and what they can do with it.&lt;/p&gt;

&lt;p&gt;A screen can have generous spacing, neat cards, and very little text while still being hard to understand. Each detail might be readable on its own. The problem is that the visitor has to assemble the explanation.&lt;/p&gt;

&lt;p&gt;Imagine a subscription screen that puts the monthly rate in one panel, the number of seats in another, and the billing interval behind a dropdown. Everything looks organized. Yet answering "What am I about to pay?" requires a small investigation.&lt;/p&gt;

&lt;p&gt;That is the distinction I want to explore: &lt;strong&gt;breaking information apart is a layout operation; putting meaning within reach is a design responsibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this third article in &lt;strong&gt;Applied Design Theory&lt;/strong&gt;, I am treating chunking as a problem of boundaries. Where should a group begin? What belongs inside it? What relationship must survive when the layout changes? And how do we know that our tidy arrangement makes sense to someone who did not build it?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Border Is Not a Meaning
&lt;/h2&gt;

&lt;p&gt;As a developer, I can create a reusable card component before I know what the card ought to represent. Give it a title, a body, and some actions, and almost anything can go inside.&lt;/p&gt;

&lt;p&gt;That flexibility is useful for implementation. It is not a content strategy.&lt;/p&gt;

&lt;p&gt;Consider an order confirmation grouped into three sections: Numbers, Text, and Status. The date and price fit under Numbers. The delivery method and address fit under Text. Payment confirmation fits under Status. It is a consistent classification, but it answers a question about the data rather than the order.&lt;/p&gt;

&lt;p&gt;The customer has a different question: "What happens to my order?"&lt;/p&gt;

&lt;p&gt;A summary that brings arrival date, shipping method, destination, total, and payment state together gives those facts a shared purpose. Nothing has been removed. The relationship has been made explicit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwtyd9juycaadbaucgles.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwtyd9juycaadbaucgles.webp" alt="The same order details grouped by data type on the left and by the customer's question on the right, keeping arrival date, shipping, address, total, and payment together." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An illustrative redesign, not a usability-study result. The change is the organizing principle, not the amount of information.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is my starting test for a proposed chunk: &lt;strong&gt;can I explain why these particular things belong together without mentioning their color, component type, or position?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "They all help someone verify the delivery," there is a meaningful unit to design. If the answer is "We needed three equal columns," the layout may be driving the wrong decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Miller Actually Gives Us
&lt;/h2&gt;

&lt;p&gt;George A. Miller's 1956 paper examined limits in information processing, including immediate memory and absolute judgment. A central idea was recoding: familiar relationships allow multiple pieces of information to be represented as a larger unit. The amount of information in a chunk matters, not just the count of raw symbols (Miller, 1956).&lt;/p&gt;

&lt;p&gt;That is more interesting for design than the famous number in the title. It also requires more care. Miller was not establishing a maximum number of navigation links, form fields, or dashboard tiles. Translating a memory experiment directly into a page-layout quota changes the task being studied. &lt;a href="https://cse.buffalo.edu/~rapaport/575/F01/miller56.html" rel="noopener noreferrer"&gt;Read Miller's original paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Later, Cowan (2001) reviewed evidence for a smaller capacity, averaging roughly four chunks under conditions that constrain rehearsal and recoding. The boundary conditions are essential to that argument. It is not a replacement rule telling designers to put four things on every screen. &lt;a href="https://doi.org/10.1017/S0140525X01003922" rel="noopener noreferrer"&gt;Read Cowan's review&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My design conclusion is deliberately narrower: do not make an interface depend on people holding disconnected facts in their heads when the interface could keep those facts available.&lt;/p&gt;

&lt;p&gt;There is a difference between remembering a set of options after they disappear and scanning a list that remains visible. Before invoking a memory limit, identify which task your user is actually performing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Different Things We Call a Chunk
&lt;/h2&gt;

&lt;p&gt;I find it useful to separate three layers during a design review. This is a working distinction for evaluating an interface, not a new psychological taxonomy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A visual group&lt;/strong&gt; says, "These elements are presented together." It might be established through a heading, spacing, alignment, or a container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A task group&lt;/strong&gt; says, "These details help answer the same question." An invoice's amount due, due date, and payment action form a useful task group even though the underlying data types differ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A learned mental chunk&lt;/strong&gt; is a unit the person already recognizes through knowledge or experience. A developer might read an HTTP method and route as one familiar operation. Someone encountering an API for the first time may need to understand each term separately.&lt;/p&gt;

&lt;p&gt;These layers can reinforce each other, but they are not interchangeable. Putting unfamiliar configuration settings inside one rectangle does not instantly make them one easy-to-process idea.&lt;/p&gt;

&lt;p&gt;Moran (2016) distinguishes the memory concept from the broader UX use of chunking to describe meaningful, visually distinct content units. Her practical guidance connects grouping with scanning while warning against using the number seven to cap menus. I use that distinction here: better scanning is a useful design goal, but it is not proof that a visual group has become a single unit in memory. &lt;a href="https://www.nngroup.com/articles/chunking/" rel="noopener noreferrer"&gt;Moran's explanation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Expertise Can Hide the Missing Explanation
&lt;/h2&gt;

&lt;p&gt;Chase and Simon's (1973) research on chess perception investigated how players of different skill levels organized positions into chunks. Their work links skilled performance to learned patterns and relationships, rather than a generally superior ability to remember any arrangement of pieces. It is evidence about chess expertise, not a direct experiment on website layouts. &lt;a href="https://doi.org/10.1016/0010-0285(73)90004-2" rel="noopener noreferrer"&gt;Read the study&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The interface implication I draw is that &lt;strong&gt;the designer's meaningful unit may still be several unresolved questions for the visitor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A settings group called "Delivery infrastructure" might seem clear to the engineer who implemented it. Inside are domains, DNS records, sending identities, and retry policies. A small-business owner trying to send a receipt may not know which setting affects which part of the process.&lt;/p&gt;

&lt;p&gt;For that audience, I would start with questions such as "What address will customers see?" and "Is this domain ready to send?" Technical details can remain available. The grouping should make an entry point possible without requiring the implementation model first.&lt;/p&gt;

&lt;p&gt;The opposite mistake is treating experienced operators as permanent beginners. A specialist comparing many records may prefer a compact table to a sequence of large explanatory panels. The goal is not maximum explanation everywhere. It is the right relationship made legible for the audience and task.&lt;/p&gt;

&lt;p&gt;That connects to &lt;a href="https://www.ryanverwey.dev/blog/the-novelty-budget-familiar-vs-novel-ui" rel="noopener noreferrer"&gt;The Novelty Budget&lt;/a&gt;: familiarity is not only about recognizable buttons. It also includes the concepts people bring to a product. Borrow those concepts carefully, and verify that your audience actually has them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the Smallest Complete Answer
&lt;/h2&gt;

&lt;p&gt;My preferred unit for designing content is the &lt;strong&gt;smallest complete answer&lt;/strong&gt;. That phrase is a design heuristic, not a measured cognitive capacity.&lt;/p&gt;

&lt;p&gt;It means a group contains enough information to answer one useful question without sending the reader elsewhere for an essential qualification.&lt;/p&gt;

&lt;p&gt;Take a scan report. A heading that says "Critical issues: 12" supplies a count, but not necessarily a useful answer. A repair-oriented group might identify the issue, show an affected page, explain the evidence, and describe the next action. Those details form a complete unit for deciding whether and how to investigate.&lt;/p&gt;

&lt;p&gt;That group is larger than a statistic tile. It may also require less reconstruction work.&lt;/p&gt;

&lt;p&gt;I would not put every occurrence, implementation note, and historical comparison into the same initial view. Completeness is relative to the current question. "What should I investigate next?" needs different detail from "How did this issue change across six releases?"&lt;/p&gt;

&lt;p&gt;Here is how I would choose boundaries for a hypothetical website audit report:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader's question&lt;/th&gt;
&lt;th&gt;Keep together&lt;/th&gt;
&lt;th&gt;Detail that can follow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What needs attention first?&lt;/td&gt;
&lt;td&gt;Issue, severity, affected scope, reason for priority&lt;/td&gt;
&lt;td&gt;Full occurrence list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the finding relevant here?&lt;/td&gt;
&lt;td&gt;Page URL, observed evidence, applicable condition&lt;/td&gt;
&lt;td&gt;Methodology notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How can I verify a repair?&lt;/td&gt;
&lt;td&gt;Expected result, check steps, limitations&lt;/td&gt;
&lt;td&gt;Historical scan comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are proposed groupings, not claims about any particular tool's measured effectiveness. Their value is that someone can disagree with them in useful terms: perhaps scope is essential earlier, or a limitation must be shown beside the severity. That is a better conversation than arguing about card padding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries Can Also Create Work
&lt;/h2&gt;

&lt;p&gt;Every group has an inside and an outside. When we draw a boundary, we make an argument that the contents belong together and the neighboring content is different.&lt;/p&gt;

&lt;p&gt;Sometimes that argument is wrong.&lt;/p&gt;

&lt;p&gt;On a subscription checkout, separating plan selection from seat quantity may be reasonable. Hiding the selected rate when someone reaches confirmation is not. The choice depends on the relationship between price basis, quantity, interval, and total.&lt;/p&gt;

&lt;p&gt;In the example below, five seats at $20 per seat per month produce a $100 monthly total before any tax. The arithmetic is simple. The design question is why the user should have to reconstruct it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31hgzldwxetwl7flyt8z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31hgzldwxetwl7flyt8z.webp" alt="A subscription flow that loses pricing context between steps compared with a visible summary showing Team plan, twenty dollars per seat per month, five seats, and a one-hundred-dollar monthly total." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The summary preserves the facts needed to authorize a purchase. The pricing and layouts are illustrative, not results from a product experiment.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I call unnecessary reconstruction across group boundaries &lt;strong&gt;reassembly work&lt;/strong&gt;. This is my own term for a review concern, not a standardized metric. Look for the moments when someone must remember a value, reopen a section, or mentally reconcile labels that should have been connected.&lt;/p&gt;

&lt;p&gt;A multi-step flow can reduce the amount presented at once while increasing reassembly work. Both can be true. The number of visible fields is therefore a weak measure of simplicity by itself.&lt;/p&gt;

&lt;p&gt;Keep a compact summary where it helps. Repeat important context intentionally. Let people edit a prior choice without losing the rest of their work. An extra line of accurate context may be better than a cleaner screen that leaves the commitment ambiguous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure Is Not the Same as Organization
&lt;/h2&gt;

&lt;p&gt;An accordion creates a visibility boundary. A tab creates a navigation boundary. Neither guarantees a useful conceptual boundary.&lt;/p&gt;

&lt;p&gt;I would use disclosure when a person can predict what is inside and does not need that information to interpret the visible content. Troubleshooting details for an uncommon error might fit. The recurring price of a subscription does not belong behind an unexplained "More" control beside a purchase button.&lt;/p&gt;

&lt;p&gt;A useful review exercise is to read only the collapsed labels. Do they let someone locate the answer, or merely announce vague categories such as General, Additional, and Other?&lt;/p&gt;

&lt;p&gt;Then open the sections in the order a real task requires. If answering a basic question means repeatedly switching between mutually exclusive panels, consider keeping those details together or allowing multiple panels to remain open.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;The Decision Tax&lt;/a&gt;, I argued that choice support matters more than a universal option limit. The same principle applies here. Hiding choices does not automatically make them easier to evaluate. Breaking information apart does not automatically make it easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Is Part of the Layout
&lt;/h2&gt;

&lt;p&gt;A heading is a promise about what follows. If it says "Before you connect your account," the section should explain prerequisites or consequences needed before that action. If it begins with product history and ends with a warning about permissions, the group has not kept its promise.&lt;/p&gt;

&lt;p&gt;For a technical guide, I would organize the initial setup around distinct reader questions: what access is required, what the command changes, what success looks like, and how to recover from failure. A code block without those boundaries can be syntactically precise and operationally unclear.&lt;/p&gt;

&lt;p&gt;Paragraph length should follow the argument, not a word quota. A short paragraph can introduce a claim. Another can explain a limitation. A numbered list suits a sequence. A table suits repeated comparison dimensions. They are different reading structures, not interchangeable ways to make a page look lighter.&lt;/p&gt;

&lt;p&gt;There is also a point where fragmentation damages the argument. If every sentence becomes a callout, nothing establishes priority. If every qualification becomes a separate section, readers must travel back and forth to understand a single claim.&lt;/p&gt;

&lt;p&gt;My editing test is to read the headings without the body, then read each section with its heading removed. The first pass tests the route through the article. The second asks whether the content actually holds together. A section that needs three unrelated headings probably contains three different jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Component Boundary Is Not a User Boundary
&lt;/h2&gt;

&lt;p&gt;Atomic component design helps a team reuse behavior and styling. It does not tell us which information should travel together for a reader.&lt;/p&gt;

&lt;p&gt;An application can render an order summary from several small components while presenting it as one coherent answer. Conversely, one large React component can contain several meaningful sections. The implementation tree and the information hierarchy serve different purposes.&lt;/p&gt;

&lt;p&gt;When building a reusable summary, I would define the content contract alongside the props. What makes this summary complete? Which labels are required? What happens when a value is unavailable? Is a missing total shown as loading, unavailable, or accidentally omitted?&lt;/p&gt;

&lt;p&gt;The failure state deserves particular attention. Suppose a delivery estimate loads separately from the rest of an order. Leaving a blank line can make the group look finished when it is not. A specific state such as "Delivery estimate unavailable" preserves the meaning of the group more honestly than an empty space.&lt;/p&gt;

&lt;p&gt;I would also check whether the visible grouping survives without styling. W3C's form guidance recommends grouping related controls both visually and programmatically, including &lt;code&gt;fieldset&lt;/code&gt; and &lt;code&gt;legend&lt;/code&gt; for appropriate control groups (World Wide Web Consortium, n.d.). A border alone does not provide that relationship to assistive technology. &lt;a href="https://www.w3.org/WAI/tutorials/forms/grouping/" rel="noopener noreferrer"&gt;W3C's grouping guidance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a delivery-method choice, the group label should identify the question, while each option has its own clear label. Elsewhere, use headings, lists, and tables according to the content rather than assigning a generic group role to every container. Semantic structure should express the relationship, not imitate a screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile Should Change the Arrangement, Not the Meaning
&lt;/h2&gt;

&lt;p&gt;Responsive design is where weak grouping decisions become especially visible.&lt;/p&gt;

&lt;p&gt;On a wide screen, a selected plan and its total might sit beside each other. At a narrow width, stacking entire columns could put several unrelated sections between them. The desktop screenshot still looks reasonable, but the mobile reading order has separated the answer.&lt;/p&gt;

&lt;p&gt;I would review the narrow layout in task order: choose a plan, adjust quantity, inspect cost, confirm. The related context should appear at the point where it is needed. The original two-column arrangement has no special right to survive.&lt;/p&gt;

&lt;p&gt;A persistent summary can help, but it can also consume too much of a small viewport. A compact inline summary near the action may be the better tradeoff. Test the actual content with zoom, long labels, validation messages, and the on-screen keyboard rather than assuming that one sticky pattern solves every case.&lt;/p&gt;

&lt;p&gt;This applies to the article's diagrams, too. Their essential lessons are also expressed in the surrounding text and descriptive alternatives. An image should illustrate the explanation, not be the only place a reader can obtain it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The BOUND Review
&lt;/h2&gt;

&lt;p&gt;To turn these questions into a repeatable critique, I use &lt;strong&gt;BOUND&lt;/strong&gt;: Begin with the question, Organize relationships, Use informative labels, Notice dependencies, and Demonstrate comprehension.&lt;/p&gt;

&lt;p&gt;This is my synthesis for design and engineering reviews. It is not a validated psychological scale, a numerical score, or a claim that five is the correct number of anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cdbarg5zbs04mc5cvgo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cdbarg5zbs04mc5cvgo.webp" alt="The BOUND review: Begin with the question, Organize relationships, Use informative labels, Notice dependencies, and Demonstrate comprehension, each paired with a practical design question." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Use the review to explain and test a boundary, not to count the objects inside it.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Begin With the Question
&lt;/h3&gt;

&lt;p&gt;Write the question the person is trying to answer before naming the component. "Can I change this booking without paying a fee?" is more useful than "We need a booking card."&lt;/p&gt;

&lt;p&gt;Specify the audience and moment, too. Someone comparing bookings and someone changing an existing reservation may need different groups even when the underlying data is identical. If the team cannot agree on the question, polishing the container is premature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organize Relationships
&lt;/h3&gt;

&lt;p&gt;Identify the facts needed together to answer that question. For a booking change, that might include the current reservation, eligible alternatives, fee conditions, and the resulting price.&lt;/p&gt;

&lt;p&gt;Then inspect the proposed group for intruders. A promotional offer might be related to the business but unrelated to the immediate decision. Being available in the same API response is not a reason for two facts to share a presentation group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Informative Labels
&lt;/h3&gt;

&lt;p&gt;Give the group a name that helps people predict its contents. "Change fees" is a stronger location cue than "Important information" when the reader is looking for a cancellation cost.&lt;/p&gt;

&lt;p&gt;Try the label outside the layout: in a navigation list, an error message, or a support conversation. If it only makes sense beside a particular icon, the wording is probably carrying too little of the meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notice Dependencies
&lt;/h3&gt;

&lt;p&gt;Draw the connections that cross your proposed boundaries. Does a later choice alter an earlier total? Does a setting depend on a permission explained elsewhere? Does an exception change the meaning of the headline?&lt;/p&gt;

&lt;p&gt;These connections are not automatically reasons to merge everything. They are reasons to preserve context deliberately. A summary, a clear reference, or a revised sequence may keep the interface understandable without creating one enormous section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demonstrate Comprehension
&lt;/h3&gt;

&lt;p&gt;Ask someone to use the interface and explain the result in their own words. Can they identify the applicable fee, predict the next step, and say what will change after confirmation?&lt;/p&gt;

&lt;p&gt;Do not settle for a preference vote between screenshots. A person can prefer the tidier version while misunderstanding a condition. The purpose of the review is to discover whether the organization supports the task, not whether the team likes its visual rhythm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Understanding, Not Just Speed
&lt;/h2&gt;

&lt;p&gt;For a hypothetical checkout test, I would give participants a specific goal: choose five seats, determine the recurring cost, and explain what they are authorizing. Keep the underlying facts equivalent between versions so a missing price or a rewritten offer does not confound the comparison.&lt;/p&gt;

&lt;p&gt;Observe where they look, which sections they reopen, and whether they reach the right interpretation. Record incorrect answers and uncertainty as well as completion time. A fast confirmation based on the wrong billing assumption is not a successful design outcome.&lt;/p&gt;

&lt;p&gt;I would include both people familiar with per-seat billing and people who rarely purchase software. The point is not to average away their differences. It is to discover whether a group that feels obvious to one audience requires explanation for another.&lt;/p&gt;

&lt;p&gt;If the study includes an interruption, make it consistent across versions. Ask participants to resume the task and see whether the visible context helps them recover. Report that as an observation from this task, not a universal percentage improvement in cognition.&lt;/p&gt;

&lt;p&gt;For an article or knowledge base, use a different test: ask readers to find a specific answer, identify its limitation, and choose an appropriate next action. Time on page alone cannot tell you whether someone was engaged, lost, or interrupted.&lt;/p&gt;

&lt;p&gt;Agree on the important failures before testing. A team should know whether the priority is finding an answer, understanding a commitment, preventing a dangerous mistake, or supporting repeated expert work. Otherwise, it is too easy to call whichever metric improved the definition of success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The most useful question is not "How many things fit in a chunk?" It is "What can someone understand because these things are together?"&lt;/p&gt;

&lt;p&gt;Start with the reader's task. Make relationships explicit. Write labels that predict the contents. Keep qualifications close to the claims they change. When a flow crosses a boundary, carry the necessary context with it.&lt;/p&gt;

&lt;p&gt;Miller gives us a reason to take meaningful organization seriously. He does not give us a universal grid specification. A beautiful container cannot supply knowledge the reader does not have, and a shorter screen is not automatically a simpler experience.&lt;/p&gt;

&lt;p&gt;Good chunking makes the structure of the information useful. It helps a person find an answer, see how the parts relate, and continue without rebuilding the whole picture in their head.&lt;/p&gt;

&lt;p&gt;That is the shape of understanding worth designing for.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Chase, W. G., &amp;amp; Simon, H. A. (1973). &lt;a href="https://doi.org/10.1016/0010-0285(73)90004-2" rel="noopener noreferrer"&gt;Perception in chess&lt;/a&gt;. &lt;em&gt;Cognitive Psychology, 4&lt;/em&gt;(1), 55-81.&lt;/p&gt;

&lt;p&gt;Cowan, N. (2001). &lt;a href="https://doi.org/10.1017/S0140525X01003922" rel="noopener noreferrer"&gt;The magical number 4 in short-term memory: A reconsideration of mental storage capacity&lt;/a&gt;. &lt;em&gt;Behavioral and Brain Sciences, 24&lt;/em&gt;(1), 87-114.&lt;/p&gt;

&lt;p&gt;Miller, G. A. (1956). &lt;a href="https://doi.org/10.1037/h0043158" rel="noopener noreferrer"&gt;The magical number seven, plus or minus two: Some limits on our capacity for processing information&lt;/a&gt;. &lt;em&gt;Psychological Review, 63&lt;/em&gt;(2), 81-97.&lt;/p&gt;

&lt;p&gt;Moran, K. (2016, March 20). &lt;em&gt;&lt;a href="https://www.nngroup.com/articles/chunking/" rel="noopener noreferrer"&gt;How chunking helps content processing&lt;/a&gt;.&lt;/em&gt; Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.). &lt;em&gt;&lt;a href="https://www.w3.org/WAI/tutorials/forms/grouping/" rel="noopener noreferrer"&gt;Grouping controls&lt;/a&gt;.&lt;/em&gt; Web Accessibility Initiative.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is chunking in UI/UX design?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chunking organizes information into meaningful, distinguishable units. In an interface, those units should help people locate an answer and understand relationships. A visual container can support that goal, but a collection of unrelated facts does not become meaningful simply because it has a border.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Miller's Law mean a screen should contain only seven items?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Miller's work on memory and information processing does not establish a seven-item screen limit. Evaluate whether people need to remember information, recognize visible options, compare facts, or learn a concept. Those tasks call for different design decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between chunking and progressive disclosure?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chunking concerns which information belongs together. Progressive disclosure concerns when information becomes visible. They can work together, but hiding details is harmful when those details are needed to understand the current decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I tell whether my groups are useful?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;State the question each group answers, check that its heading predicts the contents, and identify dependencies that cross its boundaries. Then observe people completing a realistic task and explaining the result. The BOUND review provides a practical structure for that process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should every chunk be a card?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. A heading and well-organized text, a form fieldset, a list, or a table may express the relationship more clearly. Choose the structure that supports the reading or interaction task rather than turning every section into a repeated visual container.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>chunking</category>
      <category>informationarchitecture</category>
      <category>interactiondesign</category>
    </item>
    <item>
      <title>The Decision Tax: When More Options Make an Interface Worse</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Tue, 08 Sep 2026 14:34:44 +0000</pubDate>
      <link>https://dev.to/rverwey/the-decision-tax-when-more-options-make-an-interface-worse-2mn</link>
      <guid>https://dev.to/rverwey/the-decision-tax-when-more-options-make-an-interface-worse-2mn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-decision-tax-choice-overload-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Choice overload is not the presence of many options. It is the point where an interface asks a person to do more comparison work than the decision feels worth.&lt;/p&gt;

&lt;p&gt;That distinction matters. A catalog with 10,000 products can feel manageable when search, filters, categories, recommendations, and comparison tools reduce the active decision to a useful set. A pricing page with four plans can feel overwhelming when the names are vague, features are inconsistent, and every card claims to be the best value.&lt;/p&gt;

&lt;p&gt;The design problem is not simply quantity. It is &lt;strong&gt;decision work&lt;/strong&gt;: how much effort users must spend understanding the choice, comparing alternatives, predicting consequences, and feeling confident enough to act.&lt;/p&gt;

&lt;p&gt;I call that accumulated effort the &lt;strong&gt;decision tax&lt;/strong&gt;. Every option adds potential value, but it can also add discovery, comparison, uncertainty, and regret. Good choice architecture preserves meaningful agency while reducing the tax required to use it.&lt;/p&gt;

&lt;p&gt;This second entry in my &lt;strong&gt;Applied Design Theory&lt;/strong&gt; series explains when choice overload is likely, why it is related to but different from Hick's Law, and how I use the SCOPE review to design better pricing pages, filters, onboarding flows, settings, and product selections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choice Overload Is a Mismatch, Not a Number
&lt;/h2&gt;

&lt;p&gt;Choice is usually valuable. It lets people express preferences, adapt a product to their situation, and avoid being forced into an unsuitable default. Removing all choice is not simplicity. It is control taken away from the user.&lt;/p&gt;

&lt;p&gt;Overload begins when the demands of the decision exceed the support provided by the interface. The same set of options can be easy for one person and exhausting for another because the user, task, consequences, and presentation have changed.&lt;/p&gt;

&lt;p&gt;Consider three people choosing a camera:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A professional photographer arrives with a specific mount, sensor requirement, and budget. A large catalog helps because their preferences are already formed.&lt;/li&gt;
&lt;li&gt;A first-time buyer knows they want better family photos but does not understand lenses, sensor formats, or stabilization. The same catalog creates a vocabulary problem before it creates a product decision.&lt;/li&gt;
&lt;li&gt;A traveler needs a replacement before a flight. Even with strong product knowledge, time pressure makes a large comparison set expensive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The number of cameras did not change. The decision did.&lt;/p&gt;

&lt;p&gt;This is why rules such as "never show more than seven options" are attractive and unreliable. They convert a contextual design problem into an easy checklist. The interface may pass the checklist while leaving the actual work untouched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fedse8vwqhwtr9m3b4qal.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fedse8vwqhwtr9m3b4qal.webp" alt="A conceptual curve showing that choice overload depends on option complexity, task difficulty, preference uncertainty, and the user's decision goal rather than a universal option count." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The overload threshold moves with the context. More options can improve fit until comparison cost and uncertainty begin to reduce confidence.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Famous Jam Study Actually Tells Us
&lt;/h2&gt;

&lt;p&gt;The most repeated choice-overload story comes from Sheena Iyengar and Mark Lepper's 2000 paper, &lt;a href="https://doi.org/10.1037/0022-3514.79.6.995" rel="noopener noreferrer"&gt;&lt;em&gt;When Choice Is Demotivating&lt;/em&gt;&lt;/a&gt;. In the field experiment, shoppers encountered a display of either 24 jams or six. The larger display attracted more attention, but nearly 30 percent of people exposed to the smaller set purchased a jar, compared with about 3 percent in the larger-set condition.&lt;/p&gt;

&lt;p&gt;The paper included two additional experiments. Students were more likely to complete an optional essay when choosing from six topics instead of 30, and participants choosing chocolates from a smaller assortment reported better experiences after the decision. Together, the studies challenged the assumption that more choice always produces more motivation and satisfaction.&lt;/p&gt;

&lt;p&gt;The useful lesson is not that six is a perfect interface number. Jam samples, essay prompts, and chocolates share particular conditions: options are similar, the ideal choice is uncertain, comparison consumes time, and choosing the wrong item is easy to imagine after the fact.&lt;/p&gt;

&lt;p&gt;The result is also a warning about product metrics. The large display won attention while the small display won purchasing. An interface can look engaging at the top of a funnel while making commitment harder at the bottom. Counting impressions, card opens, or time on page without measuring completion can reward the very friction the product should remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Research Does Not Support a Universal Limit
&lt;/h2&gt;

&lt;p&gt;Choice overload became a popular design explanation because it feels immediately true. Anyone who has stared at a wall of nearly identical televisions or compared a dozen software plans recognizes the experience. Research after the original studies, however, makes the conclusion more precise.&lt;/p&gt;

&lt;p&gt;Scheibehenne, Greifeneder, and Todd's 2010 &lt;a href="https://doi.org/10.1086/651235" rel="noopener noreferrer"&gt;meta-analysis of choice-overload experiments&lt;/a&gt; examined 63 conditions from 50 published and unpublished experiments with 5,036 participants. The average effect was close to zero, while the variation between studies was substantial. In some contexts, larger assortments harmed decisions. In others, they helped or made little difference.&lt;/p&gt;

&lt;p&gt;That is not evidence that choice overload is imaginary. It is evidence that assortment size alone is an incomplete explanation.&lt;/p&gt;

&lt;p&gt;Chernev, Böckenholt, and Goodman's later &lt;a href="https://doi.org/10.1016/j.jcps.2014.08.002" rel="noopener noreferrer"&gt;conceptual review and meta-analysis&lt;/a&gt; analyzed 99 observations involving 7,202 participants and identified four conditions that reliably shape overload risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choice-set complexity:&lt;/strong&gt; Options contain many attributes, attributes are difficult to compare, or alternatives are very similar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision-task difficulty:&lt;/strong&gt; The user faces time pressure, incomplete information, uncertain consequences, or a demanding evaluation process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preference uncertainty:&lt;/strong&gt; The user does not yet know which attributes matter or how to trade one benefit against another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision goal:&lt;/strong&gt; The user wants to minimize effort rather than invest deeply in finding an optimal result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives designers a stronger model. The question is not "How many options are too many?" It is "How much unresolved decision work have we placed in front of this person?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Choice Overload and Hick's Law Are Related, Not Interchangeable
&lt;/h2&gt;

&lt;p&gt;Choice overload is often paired with Hick's Law, but they describe different outcomes.&lt;/p&gt;

&lt;p&gt;W. E. Hick's 1952 experiments on &lt;a href="https://doi.org/10.1080/17470215208416600" rel="noopener noreferrer"&gt;the rate of information gain&lt;/a&gt; studied choice reaction time. As the uncertainty among possible stimulus-response pairs increased, reaction time increased in a predictable relationship. In interface language, selecting one item from a larger set of equally plausible actions generally takes longer than selecting from a smaller set.&lt;/p&gt;

&lt;p&gt;Choice overload reaches beyond reaction time. It can involve postponing the decision, abandoning it, feeling less satisfied afterward, or continuing to wonder whether an unchosen option was better.&lt;/p&gt;

&lt;p&gt;This difference changes how I apply the concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hick's Law helps evaluate &lt;strong&gt;selection speed&lt;/strong&gt; among visible actions.&lt;/li&gt;
&lt;li&gt;Choice overload helps evaluate &lt;strong&gt;motivation, confidence, completion, and satisfaction&lt;/strong&gt; across a decision.&lt;/li&gt;
&lt;li&gt;Hick's Law is most direct when options are known and the task is to select.&lt;/li&gt;
&lt;li&gt;Choice overload becomes especially relevant when users must first understand what the options mean and decide what they value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A navigation menu with twelve familiar destinations may slow scanning without creating meaningful overload. Three health-insurance plans can create overload because the consequences are important and the tradeoffs are difficult. Counting options without examining the decision hides that difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Tax
&lt;/h2&gt;

&lt;p&gt;The decision tax is the total work an interface transfers to the user before they can choose with confidence. I break it into five costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery Cost
&lt;/h3&gt;

&lt;p&gt;What must the user inspect before they can form a reasonable shortlist?&lt;/p&gt;

&lt;p&gt;Discovery cost rises when every option receives equal visual weight, labels are vague, categories reflect the company's organization instead of the user's goal, or important differences are buried in detail pages.&lt;/p&gt;

&lt;p&gt;Search and filters can lower discovery cost, but only when their vocabulary matches what people know. A filter labeled "deployment topology" may be technically precise and useless to a buyer who thinks in terms of "works with my existing server."&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison Cost
&lt;/h3&gt;

&lt;p&gt;How much work is required to compare the remaining options on the same dimensions?&lt;/p&gt;

&lt;p&gt;Comparison becomes expensive when cards describe different attributes, units change, features appear in inconsistent orders, or the user must remember facts while moving between pages. A side-by-side table is useful because it externalizes memory and aligns differences. It does not help when the table contains 80 undifferentiated rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interpretation Cost
&lt;/h3&gt;

&lt;p&gt;Does the user understand why a difference matters?&lt;/p&gt;

&lt;p&gt;Technical products often expose implementation details before translating their consequences. Storage, requests, seats, integrations, response time, and support levels matter only after the user can connect them to a real need. Good content gives the attribute a job instead of presenting it as a number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consequence Cost
&lt;/h3&gt;

&lt;p&gt;How difficult or risky is it to be wrong?&lt;/p&gt;

&lt;p&gt;Choosing a playlist theme has a low consequence cost. Selecting a billing plan, deleting data, changing permissions, or configuring a deployment has a higher one. High-consequence choices need clearer previews, stronger comparison, confirmation, reversibility, and honest explanations of what changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regret Cost
&lt;/h3&gt;

&lt;p&gt;How much does the interface keep unchosen alternatives psychologically active after the decision?&lt;/p&gt;

&lt;p&gt;When every option claims a unique advantage, users can imagine a benefit they sacrificed no matter what they select. Clear tradeoffs, a transparent default, easy plan changes, and a reversible trial reduce the pressure to make a permanently perfect choice.&lt;/p&gt;

&lt;p&gt;The decision tax explains why simply deleting options is an incomplete fix. A team can reduce six plans to three and still leave every cost in place. Better design removes unnecessary work while preserving options that represent real differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Catalog Is Not a Decision Surface
&lt;/h2&gt;

&lt;p&gt;One of the most useful distinctions in interface design is the difference between how many options a system supports and how many it asks the user to evaluate at once.&lt;/p&gt;

&lt;p&gt;An online store may need a large catalog. A developer tool may support dozens of integrations. A dashboard may expose hundreds of reports. The product does not need to flatten that inventory into one visual decision.&lt;/p&gt;

&lt;p&gt;The interface can stage the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;search when the user knows the target&lt;/li&gt;
&lt;li&gt;categories when the user recognizes a goal&lt;/li&gt;
&lt;li&gt;filters when attributes are understood&lt;/li&gt;
&lt;li&gt;guided questions when preferences are still forming&lt;/li&gt;
&lt;li&gt;recommendations when the reason can be explained&lt;/li&gt;
&lt;li&gt;comparison when a shortlist exists&lt;/li&gt;
&lt;li&gt;progressive disclosure when detail becomes relevant later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not hiding choice. It is sequencing choice while keeping the broader catalog reachable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison Is an Interface, Not a Spreadsheet Dump
&lt;/h2&gt;

&lt;p&gt;When options are genuinely different, comparison is necessary. The design responsibility is to make those differences legible.&lt;/p&gt;

&lt;p&gt;Pricing pages expose this problem clearly. Teams often add plans over time, write each card independently, attach multiple badges, and list whichever features make that tier sound strongest. The cards become advertisements rather than a comparison surface.&lt;/p&gt;

&lt;p&gt;A useful comparison aligns the options around shared questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is this for?&lt;/li&gt;
&lt;li&gt;What limit changes?&lt;/li&gt;
&lt;li&gt;Which capability becomes available?&lt;/li&gt;
&lt;li&gt;What remains the same?&lt;/li&gt;
&lt;li&gt;What happens if the user outgrows the choice?&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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh2q3t0zgtdv5axdre2ut.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh2q3t0zgtdv5axdre2ut.webp" alt="A before-and-after pricing example contrasting six inconsistent option cards with three aligned plans and a shared feature comparison." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The better surface does not pretend the other choices do not exist. It gives users common criteria, an explainable recommendation, and a path to full detail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The recommendation deserves special care. A "Most Popular" badge may reduce effort, but it can also become a dark pattern when it exists only to steer buyers toward the most profitable tier. An honest default states the reason: "Best for teams that need approvals" is more useful than "Best Value." The recommendation should help the user map a plan to a situation, not borrow authority the interface has not earned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve Agency While Reducing Work
&lt;/h2&gt;

&lt;p&gt;Choice architecture becomes manipulative when simplification quietly removes meaningful alternatives or preselects the outcome that benefits the business. The goal is not to make users choose faster at any cost. The goal is to make the decision understandable.&lt;/p&gt;

&lt;p&gt;I use five principles:&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer Meaningful Differences
&lt;/h3&gt;

&lt;p&gt;Options should represent real tradeoffs. If two plans differ only because the product team wants another price anchor, the interface inherits comparison work without adding meaningful agency.&lt;/p&gt;

&lt;p&gt;Before designing cards, ask whether each option deserves to exist. Information architecture cannot permanently repair a weak product architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give the User a Starting Point
&lt;/h3&gt;

&lt;p&gt;A featured option, saved configuration, sensible default, or short guided question can reduce the blank-page problem. The starting point should be transparent and reversible.&lt;/p&gt;

&lt;p&gt;Defaults are especially powerful, which means they require restraint. Use them to express a safe, common choice, not to sneak consent, upgrades, or recurring charges past the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make Tradeoffs Comparable
&lt;/h3&gt;

&lt;p&gt;Use consistent labels, units, order, and levels of detail. Keep the attributes people use to decide visible together. Move secondary specifications behind expansion only after the primary differences are clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let People Narrow Before They Compare
&lt;/h3&gt;

&lt;p&gt;Filtering is not merely a catalog feature. It is a decision-making tool. Good filters remove irrelevant options based on criteria the user understands and preserve the state so the user can revise the shortlist without starting over.&lt;/p&gt;

&lt;p&gt;This is one reason I prefer semantic, URL-backed filters for substantial content and product collections. They make the current decision state visible, navigable, and shareable. When implemented server-side, they can also preserve crawlable source content while keeping filtered utility URLs out of the index through deliberate canonical and robots behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the Full Set Reachable
&lt;/h3&gt;

&lt;p&gt;Progressive disclosure should reduce immediate demand without creating a dead end. "Show all," "Compare features," and removable filter chips give people control over how much complexity they want to take on.&lt;/p&gt;

&lt;p&gt;The interface should support both the person who wants guidance and the expert who already knows what they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SCOPE Review
&lt;/h2&gt;

&lt;p&gt;Before I ship a decision-heavy interface, I review it through five questions: Set, Compare, Organize, Progressively reveal, and Enable narrowing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhp1o5dcrv3450wfnvn8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhp1o5dcrv3450wfnvn8.webp" alt="The SCOPE review: set the decision, compare consistently, organize meaningfully, progressively reveal detail, and enable users to narrow the set." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SCOPE is not a rule for making every interface smaller. It is a review for making the user's decision clearer.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  S: Set the Decision
&lt;/h3&gt;

&lt;p&gt;What is the user actually choosing?&lt;/p&gt;

&lt;p&gt;This sounds obvious, but interfaces often mix several decisions in one surface. A checkout may ask the user to select shipping speed, insurance, gift options, payment method, account creation, and marketing consent without separating their purposes.&lt;/p&gt;

&lt;p&gt;Write the decision as one sentence: "Choose the plan that supports your team size and approval needs." If the sentence needs several unrelated clauses, the interface may need stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  C: Compare Consistently
&lt;/h3&gt;

&lt;p&gt;Can alternatives be evaluated on shared dimensions?&lt;/p&gt;

&lt;p&gt;Normalize the data before designing the cards. If one product lists capacity, another lists speed, and a third lists compatibility, visual polish will not make them comparable. Define the decision attributes, units, missing-value behavior, and display order as part of the product model.&lt;/p&gt;

&lt;p&gt;Consistency also matters for accessibility. Repeated structures should preserve heading order, label controls clearly, and avoid making color or position the only signal for a difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  O: Organize Meaningfully
&lt;/h3&gt;

&lt;p&gt;Can users recognize useful groups before inspecting every item?&lt;/p&gt;

&lt;p&gt;Group by user goal, task, compatibility, or another distinction that helps eliminate irrelevant options. Avoid categories that exist only because they mirror internal departments or database fields.&lt;/p&gt;

&lt;p&gt;Organization should reduce scanning without implying false exclusivity. A tool can belong to more than one useful category when the underlying model supports it.&lt;/p&gt;

&lt;h3&gt;
  
  
  P: Progressively Reveal
&lt;/h3&gt;

&lt;p&gt;What information is required for this stage of the decision?&lt;/p&gt;

&lt;p&gt;Show enough to understand the tradeoff, then provide detail on demand. Progressive disclosure fails when essential costs, restrictions, or consequences are hidden behind interaction. It also fails when the collapsed label is too vague to predict what it contains.&lt;/p&gt;

&lt;p&gt;The right test is not whether content fits above the fold. It is whether the user can make the next decision without carrying unnecessary information in working memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  E: Enable Narrowing
&lt;/h3&gt;

&lt;p&gt;Can users reduce the set using criteria they understand?&lt;/p&gt;

&lt;p&gt;Search, filters, sort controls, guided questions, and comparison trays serve different forms of intent. Search supports a known target. Filters support known attributes. Guided questions help form preferences. Comparison supports a shortlist.&lt;/p&gt;

&lt;p&gt;Do not force one control to perform every job. A search field cannot replace a useful category system, and dozens of filters cannot help someone who does not know what the attributes mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Choice Architecture in the Front End
&lt;/h2&gt;

&lt;p&gt;Choice overload is not solved only in a design file. The implementation determines whether the support survives real content, responsive layouts, assistive technology, and changing product data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Options Around Comparable Attributes
&lt;/h3&gt;

&lt;p&gt;Use structured data for the facts that drive comparison. A pricing tier should not store its entire meaning in an arbitrary marketing paragraph. Team size, limits, included capabilities, support level, and upgrade behavior should be fields that can be rendered consistently.&lt;/p&gt;

&lt;p&gt;Structured attributes also make it possible to test missing values, prevent contradictory claims, generate accessible tables, and reuse the same source across cards, comparison views, and metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep Filter State Understandable
&lt;/h3&gt;

&lt;p&gt;The selected state should survive navigation, refresh, and sharing when the task benefits from it. URL parameters are often appropriate for filters, but they need deliberate SEO handling. The canonical collection remains the indexable source while utility combinations can be &lt;code&gt;noindex, follow&lt;/code&gt; when they do not represent distinct search destinations.&lt;/p&gt;

&lt;p&gt;This avoids solving a UX problem by creating an indexing problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Native Controls
&lt;/h3&gt;

&lt;p&gt;Native selects, checkboxes, radio groups, buttons, and links provide semantics and keyboard behavior that custom clickable containers must rebuild. A plan card can be visually rich while its selection control remains a real radio input with a visible label.&lt;/p&gt;

&lt;p&gt;For progressive disclosure, the trigger needs an accessible name, an expanded state, and predictable focus behavior. For comparison tables, row and column headers need semantic relationships. For recommendations, the reason must be available as text, not only a colored badge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design the Small Screen as a Decision Sequence
&lt;/h3&gt;

&lt;p&gt;A desktop comparison table cannot simply shrink until it becomes unreadable. On mobile, preserve the shared criteria and let users compare a small number of options at a time. Sticky option headers, a two-item comparison tray, or criterion-by-criterion sections can maintain context without forcing horizontal memory.&lt;/p&gt;

&lt;p&gt;Responsive design should change the presentation, not erase the information needed to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Measure Choice Overload
&lt;/h2&gt;

&lt;p&gt;Overload is easy to label after a design review and harder to prove. I look for a pattern across behavior rather than one metric.&lt;/p&gt;

&lt;p&gt;Useful signals include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time to first meaningful action:&lt;/strong&gt; How long does it take to begin narrowing or comparing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision completion:&lt;/strong&gt; Do people finish, postpone, or abandon the choice?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backtracking:&lt;/strong&gt; How often do users reopen options or repeat the same comparison?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selection volatility:&lt;/strong&gt; How frequently does the choice change before commitment?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support-tool use:&lt;/strong&gt; Are filters, comparisons, recommendations, or guided questions helping?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors and reversals:&lt;/strong&gt; Do people immediately undo, downgrade, cancel, or contact support?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence:&lt;/strong&gt; Can users explain why their selected option fits their needs?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In moderated research, I ask participants to state their decision criteria before they begin. If they cannot, I watch whether the interface helps them form those criteria or simply makes them inspect more cards.&lt;/p&gt;

&lt;p&gt;The experiment should compare decision support, not only assortment size. Test a raw set against grouped options, aligned comparison, guided narrowing, or a transparent default. That tells the team which form of support reduces the tax without removing needed choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  When More Choice Is the Better Design
&lt;/h2&gt;

&lt;p&gt;Large assortments can be a feature when the interface and context support them.&lt;/p&gt;

&lt;p&gt;More choice is useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users arrive with clear preferences or a known target&lt;/li&gt;
&lt;li&gt;meaningful variation exists across the audience&lt;/li&gt;
&lt;li&gt;search and filters quickly remove irrelevant options&lt;/li&gt;
&lt;li&gt;experts need precision or control&lt;/li&gt;
&lt;li&gt;the task is repeated and users can build fluency&lt;/li&gt;
&lt;li&gt;alternatives are easy to compare on stable attributes&lt;/li&gt;
&lt;li&gt;choosing incorrectly is inexpensive or reversible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right move is often layered access: a strong default for common needs, guided controls for developing preferences, and complete control for experts.&lt;/p&gt;

&lt;p&gt;That is the same principle I use in &lt;a href="https://www.ryanverwey.dev/blog/the-novelty-budget-familiar-vs-novel-ui" rel="noopener noreferrer"&gt;The Novelty Budget&lt;/a&gt;. Familiar structure creates a stable core. Product-specific capability can live at the edge. Here, the stable core is a comprehensible decision, and the edge is the breadth of choice the product genuinely needs to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Team Review
&lt;/h2&gt;

&lt;p&gt;Before release, I ask the team to answer these questions together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; Which options represent meaningful differences, and which exist for internal reasons?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design:&lt;/strong&gt; Can a user identify the decision and the primary tradeoffs without opening every option?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content and engineering:&lt;/strong&gt; Are labels clear and attributes structured consistently enough for comparison?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility:&lt;/strong&gt; Can people compare without relying on pointer precision, color, or visual memory?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research and analytics:&lt;/strong&gt; Are we measuring confident completion, reversals, and abandonment instead of attention alone?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps choice overload from becoming a cosmetic diagnosis. A confusing surface may expose inconsistent tiers, missing content strategy, or weak data modeling. The interface is often where organizational ambiguity becomes visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;More choice is not automatically empowering, and less choice is not automatically usable.&lt;/p&gt;

&lt;p&gt;Choice overload appears when the interface presents more unresolved comparison, interpretation, consequence, and regret than the user can justify for the task. The threshold moves with the person, the options, the stakes, and the support built around the decision.&lt;/p&gt;

&lt;p&gt;Do not chase a magic number. Define the decision. Align the differences. Organize around user goals. Reveal detail when it becomes useful. Give people tools to narrow the set, and keep the broader choice reachable when it represents real value.&lt;/p&gt;

&lt;p&gt;The goal is not to choose for the user. It is to stop charging them unnecessary work for the right to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Chernev, A., Böckenholt, U., &amp;amp; Goodman, J. (2015). &lt;a href="https://doi.org/10.1016/j.jcps.2014.08.002" rel="noopener noreferrer"&gt;Choice overload: A conceptual review and meta-analysis&lt;/a&gt;. &lt;em&gt;Journal of Consumer Psychology, 25&lt;/em&gt;(2), 333-358.&lt;/p&gt;

&lt;p&gt;Hick, W. E. (1952). &lt;a href="https://doi.org/10.1080/17470215208416600" rel="noopener noreferrer"&gt;On the rate of gain of information&lt;/a&gt;. &lt;em&gt;Quarterly Journal of Experimental Psychology, 4&lt;/em&gt;(1), 11-26.&lt;/p&gt;

&lt;p&gt;Iyengar, S. S., &amp;amp; Lepper, M. R. (2000). &lt;a href="https://doi.org/10.1037/0022-3514.79.6.995" rel="noopener noreferrer"&gt;When choice is demotivating: Can one desire too much of a good thing?&lt;/a&gt;. &lt;em&gt;Journal of Personality and Social Psychology, 79&lt;/em&gt;(6), 995-1006.&lt;/p&gt;

&lt;p&gt;Scheibehenne, B., Greifeneder, R., &amp;amp; Todd, P. M. (2010). &lt;a href="https://doi.org/10.1086/651235" rel="noopener noreferrer"&gt;Can there ever be too many options? A meta-analytic review of choice overload&lt;/a&gt;. &lt;em&gt;Journal of Consumer Research, 37&lt;/em&gt;(3), 409-425.&lt;/p&gt;

&lt;p&gt;Toffler, A. (1970). &lt;a href="https://search.worldcat.org/title/Future-shock/oclc/570341844" rel="noopener noreferrer"&gt;&lt;em&gt;Future shock&lt;/em&gt;&lt;/a&gt;. Random House.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is choice overload in UI/UX design?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Choice overload is the decline in motivation, confidence, completion, or satisfaction that can occur when a decision requires more comparison work than a user can comfortably manage. It depends on the complexity of the options, the task, the user's preferences, and the consequences, not only the number of choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is choice overload the same as the paradox of choice?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The terms are often used interchangeably. Both describe situations where additional options can make choosing harder or less satisfying. In interface work, "choice overload" is useful because it focuses attention on observable decision behavior and the conditions producing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is choice overload different from Hick's Law?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hick's Law describes how decision time changes as uncertainty among possible responses increases. Choice overload concerns broader outcomes such as deferral, abandonment, confidence, regret, and satisfaction. A selection can take longer without becoming overload, and a small high-stakes set can create overload even when it contains few options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many options should an interface show?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no universal limit. Show enough options to represent meaningful differences, then reduce decision work through grouping, consistent comparison, progressive disclosure, defaults, search, and filtering. Test whether users can explain and complete the decision confidently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do filters solve choice overload?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Filters help when users understand the attributes and can identify what matters. They are less useful when preferences are still forming or labels use unfamiliar language. In those cases, guided questions, recommendations with reasons, and clear categories may provide a better starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the SCOPE review?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SCOPE is my five-part review for decision-heavy interfaces: Set the decision, Compare consistently, Organize meaningfully, Progressively reveal detail, and Enable narrowing. It is designed to reduce unnecessary decision work without removing meaningful user choice.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>choicearchitecture</category>
      <category>interactiondesign</category>
      <category>userresearch</category>
    </item>
    <item>
      <title>The Novelty Budget: Why Good Interfaces Borrow Before They Invent</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Tue, 01 Sep 2026 14:34:24 +0000</pubDate>
      <link>https://dev.to/rverwey/the-novelty-budget-why-good-interfaces-borrow-before-they-invent-blf</link>
      <guid>https://dev.to/rverwey/the-novelty-budget-why-good-interfaces-borrow-before-they-invent-blf</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/the-novelty-budget-familiar-vs-novel-ui" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Familiar interfaces are not successful because designers lack imagination. They are successful because users arrive with experience already installed. A cart icon, an underlined link, a Back action, a visible form label, and a predictable checkout sequence let people act before they have studied the product.&lt;/p&gt;

&lt;p&gt;Novel interfaces are not automatically brave, innovative, or better. Every unfamiliar behavior asks the user to stop, inspect, predict, test, and remember. That learning can be worthwhile, but only when the product returns more value than the interaction costs to understand.&lt;/p&gt;

&lt;p&gt;My working rule is simple: &lt;strong&gt;every interface has a novelty budget&lt;/strong&gt;. Spend it on the part of the experience that makes the product meaningfully different. Borrow the rest from patterns people already know.&lt;/p&gt;

&lt;p&gt;This first entry in my &lt;strong&gt;Interface Tensions&lt;/strong&gt; series explains how I decide what to preserve, what to challenge, and what evidence I want before an unfamiliar pattern ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Familiarity Is Stored Work
&lt;/h2&gt;

&lt;p&gt;When a user recognizes a pattern, they are not beginning from zero. They are applying prior practice from other products, physical objects, and repeated tasks. The designer benefits from work that someone else already paid for.&lt;/p&gt;

&lt;p&gt;That is the useful core of Jakob's Law: people spend most of their time using products other than ours, so their expectations are shaped elsewhere. Megan Chan's discussion of &lt;a href="https://www.nngroup.com/articles/mental-models/" rel="noopener noreferrer"&gt;mental models&lt;/a&gt; describes those expectations as beliefs people use to predict what a system will do. The belief does not have to be technically accurate to guide behavior. If an interface looks like a search field, people expect it to search. If a logo sits in a site header, many people expect it to return them home. If Back looks available, they expect it to reverse the last navigational step.&lt;/p&gt;

&lt;p&gt;This is why familiarity is more than a visual preference. It is stored work. It carries four practical advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Faster orientation.&lt;/strong&gt; People can identify where they are and what they can do without reading instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower memory demand.&lt;/strong&gt; Visible, recognizable options reduce the need to recall commands or hidden rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More confident prediction.&lt;/strong&gt; People can estimate what will happen before they act.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster recovery.&lt;/strong&gt; Familiar error messages, undo actions, and Back behavior help people recover when something goes wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Raluca Budiu's explanation of &lt;a href="https://www.nngroup.com/articles/recognition-and-recall/" rel="noopener noreferrer"&gt;recognition versus recall&lt;/a&gt; matters here. Recognition gives memory more cues. A visible list of options is easier to work with than a blank field that expects the user to remember a command. A labeled icon is usually easier to interpret than an icon whose meaning must be learned. A recent-history list is easier than reconstructing where you were from memory.&lt;/p&gt;

&lt;p&gt;The strongest interfaces often feel obvious not because they are simple underneath, but because they preserve enough familiar cues that the underlying complexity does not become the user's first problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpko861622q589qmas5bq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpko861622q589qmas5bq.webp" alt="The novelty budget shows familiar conventions at a low orientation cost and strategic novelty at a higher cost that must create durable value." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The novelty budget treats unfamiliarity as a real product cost. The cost is justified only when the benefit survives beyond the first moment of surprise.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Novelty Is a Cost Before It Becomes a Capability
&lt;/h2&gt;

&lt;p&gt;Design teams experience a product differently from the people using it. We see the same screens for weeks or months. We know the vocabulary, the edge cases, the roadmap, and the logic behind every exception. Familiarity can make a working interface feel stale to the team long before it feels stale to the user.&lt;/p&gt;

&lt;p&gt;That imbalance is dangerous. A redesign can solve the team's boredom while creating the user's confusion.&lt;/p&gt;

&lt;p&gt;Nielsen Norman Group's article on the &lt;a href="https://www.nngroup.com/articles/power-law-learning/" rel="noopener noreferrer"&gt;power law of learning&lt;/a&gt; makes the underlying tradeoff clear: a common pattern benefits from practice accumulated across many products, while a new pattern begins at the steep end of its learning curve. The new design may eventually become more efficient, but the user has to remain engaged long enough to reach that point.&lt;/p&gt;

&lt;p&gt;This does not mean novelty is bad. It means novelty has an invoice.&lt;/p&gt;

&lt;p&gt;An unfamiliar pattern may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;additional explanation or contextual guidance&lt;/li&gt;
&lt;li&gt;clearer system feedback after each action&lt;/li&gt;
&lt;li&gt;a fallback for users who do not discover the new behavior&lt;/li&gt;
&lt;li&gt;stronger error recovery because prediction is weaker&lt;/li&gt;
&lt;li&gt;accessibility testing across keyboard, touch, zoom, and assistive technology&lt;/li&gt;
&lt;li&gt;repeated exposure before the interaction becomes efficient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those costs are reasonable when the new behavior unlocks something meaningful. They are wasteful when the change exists mainly to make a common task look distinctive.&lt;/p&gt;

&lt;p&gt;Replacing a conventional checkout flow with a radial gesture menu is novelty attached to a high-risk commodity task. Giving a data analyst a new way to manipulate a complex relationship directly on a canvas may be novelty attached to the actual value of the product. Both interactions can look original. Only one has a credible reason to ask the user to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Difference Between Visual Novelty and Behavioral Novelty
&lt;/h2&gt;

&lt;p&gt;One reason teams argue past each other is that "novel" can describe several different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual novelty&lt;/strong&gt; changes how the experience feels. It can come from typography, illustration, color relationships, composition, rhythm, photography, or motion. A page can look unmistakably original while its links, forms, menus, and controls remain conventional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural novelty&lt;/strong&gt; changes where information lives or how the experience is organized. A product may replace a familiar top navigation with a persistent workspace rail, reorganize content around objects instead of pages, or introduce a new hierarchy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavioral novelty&lt;/strong&gt; changes what actions mean and how the user performs them. This is the most expensive form because it changes prediction. The same gesture may produce a different result, a familiar control may behave in an unfamiliar way, or a task may require a sequence the user has never practiced.&lt;/p&gt;

&lt;p&gt;These forms should not be treated as equally risky. Visual expression can create a memorable brand without forcing the user to relearn basic operation. Structural change requires stronger orientation. Behavioral change needs the strongest justification, feedback, and testing.&lt;/p&gt;

&lt;p&gt;That separation gives a team more room than the familiar-versus-novel debate usually suggests. We do not have to choose between a generic interface and an experimental one. We can keep the interaction contract familiar while making the experience visually specific to the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Layers of Interface Change
&lt;/h2&gt;

&lt;p&gt;I use three layers to decide where invention belongs: safety, product, and expression.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjo7fr7hs0op9ujvdbrf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjo7fr7hs0op9ujvdbrf.webp" alt="Three layers of interface change: preserve conventions in the safety layer, challenge them selectively in the product layer, and differentiate freely in the expression layer." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The safest approach is not uniform conservatism. It is assigning a different level of freedom to each layer of the experience.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Safety Layer: Preserve the Contract
&lt;/h3&gt;

&lt;p&gt;The safety layer includes the actions people rely on to move, understand consequences, submit information, and recover. It covers navigation, form labels, focus behavior, destructive actions, confirmation, undo, status, and error messages.&lt;/p&gt;

&lt;p&gt;This layer should usually be boring in the best possible way. A destructive button should look and behave like a destructive action. A keyboard user should encounter controls in a meaningful order. A required field should be identified before submission. A Back action should not secretly jump to an unrelated hierarchy level.&lt;/p&gt;

&lt;p&gt;Consistency here is also an accessibility concern. The W3C guidance for &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/consistent-navigation.html" rel="noopener noreferrer"&gt;WCAG 2.2 Success Criterion 3.2.3&lt;/a&gt; explains that repeated navigation in a consistent relative order helps people predict where functions will appear. That predictability is particularly important for users working through screen magnification, sequential screen-reader output, cognitive limitations, or limited motor input.&lt;/p&gt;

&lt;p&gt;Novelty in the safety layer can make the product memorable for the wrong reason. If a person has to learn your unique meaning for Save, Close, Back, Delete, Search, or Submit, the interface is spending attention on infrastructure instead of value.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Product Layer: Challenge Selectively
&lt;/h3&gt;

&lt;p&gt;The product layer is where the application performs its defining work. This is the most defensible place for behavioral novelty because a better interaction can become a real capability.&lt;/p&gt;

&lt;p&gt;An AI-assisted editor may need a new pattern for reviewing proposed changes before they are applied. A spatial planning tool may need direct manipulation that has no close equivalent in a document interface. A collaboration product may need a new way to show presence, ownership, or branching work. A complex audit product may need to turn hundreds of findings into a prioritized repair plan instead of another flat report.&lt;/p&gt;

&lt;p&gt;This is where I am willing to challenge convention, but I still want familiar anchors around the new behavior. The user should recognize the objects, labels, state changes, and recovery options even if the core workflow is new.&lt;/p&gt;

&lt;p&gt;While building &lt;a href="https://www.ryanverwey.dev/blog/building-optimize-echo-effect-audit-tool" rel="noopener noreferrer"&gt;Optimize&lt;/a&gt;, for example, the distinctive value was not inventing a strange new way to click through a report. The value was in turning SEO, AEO, GEO, crawler, structured-data, and accessibility findings into a clearer decision surface. Familiar report organization provided the foothold. The product logic supplied the difference.&lt;/p&gt;

&lt;p&gt;The same principle shapes design-system work. In &lt;a href="https://www.ryanverwey.dev/blog/atomic-design-system-angular" rel="noopener noreferrer"&gt;building an Angular design system&lt;/a&gt;, I found that consistency at the component and token level creates room for teams to solve higher-order product problems. If every button, form field, spacing decision, or responsive pattern is reinvented locally, the organization spends its novelty budget before it reaches anything customers value.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Expression Layer: Differentiate Deliberately
&lt;/h3&gt;

&lt;p&gt;The expression layer includes typography, imagery, pacing, transitions, tone, editorial composition, and moments of delight. This layer can carry far more novelty because it can create distinction without changing the meaning of basic actions.&lt;/p&gt;

&lt;p&gt;A portfolio, cultural story, game, campaign, or product launch may benefit from visual surprise and exploration. An operational dashboard used throughout a workday usually needs more restraint. The right amount of expression depends on context, but expression is generally a safer place to be bold than navigation or error recovery.&lt;/p&gt;

&lt;p&gt;The important distinction is that visual originality should support comprehension, not compete with it. Motion can show a relationship, reveal state, or guide attention. It can also delay access and make the user wait for the designer's performance to finish. Typography can establish a strong voice. It can also reduce readability. An unusual layout can create a memorable sequence. It can also hide the next action.&lt;/p&gt;

&lt;p&gt;Novelty becomes useful when it clarifies the product's identity or content while leaving the interaction contract intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Familiarity Is Not the Same as Sameness
&lt;/h2&gt;

&lt;p&gt;Following conventions does not require cloning competitors. A familiar pattern describes an expectation, not a finished appearance.&lt;/p&gt;

&lt;p&gt;Consider a card. The convention is not a particular shadow, border radius, or color. The convention is that related information is grouped into a recognizable unit, often with a clear hierarchy and an action that belongs to that unit. A team can express that relationship through editorial spacing, a dense utility row, a bordered region, an image-led layout, or no visible container at all.&lt;/p&gt;

&lt;p&gt;The same is true for navigation. Familiarity means users can identify where navigation is, understand the available destinations, perceive the current location, and operate it with the inputs they use. It does not require every site to share the same typography or visual composition.&lt;/p&gt;

&lt;p&gt;This is where design systems are often misunderstood. A design system should not flatten every product into one visual template. It should stabilize the decisions that benefit from repetition so designers and developers can spend their attention on context. Tokens, accessible controls, interaction states, and semantic patterns create a dependable grammar. Products still decide what to say with it.&lt;/p&gt;

&lt;p&gt;The goal is not sameness. The goal is transferable confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Novelty Earns Its Place
&lt;/h2&gt;

&lt;p&gt;Novelty has the strongest case in four situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Existing Pattern Cannot Express the Capability
&lt;/h3&gt;

&lt;p&gt;Sometimes a product does something that familiar controls do not represent well. Forcing a new capability into an old metaphor can create more confusion than teaching a new pattern.&lt;/p&gt;

&lt;p&gt;The test is whether the innovation changes the user's power, not merely the presentation. If a new interaction lets people see relationships, compare alternatives, automate a repeated task, or collaborate in a way the old pattern could not support, the learning cost may have a durable return.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repetition Will Repay the Learning
&lt;/h3&gt;

&lt;p&gt;An interaction used once must be immediately understandable. An interaction used dozens of times a day can justify a short learning curve if it becomes significantly faster or clearer with practice.&lt;/p&gt;

&lt;p&gt;This is why expert tools can support shortcuts, direct manipulation, command palettes, and dense controls that would be inappropriate in a one-time consumer flow. The user has repeated opportunities to convert learning into fluency.&lt;/p&gt;

&lt;p&gt;Frequency does not excuse a bad first experience. It changes the return calculation. The product still needs clear onboarding, visible feedback, and a path back to safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploration Is the Task
&lt;/h3&gt;

&lt;p&gt;Some experiences are built to encourage discovery rather than rapid completion. Interactive storytelling, data exploration, creative tools, and games may use surprise as part of the value.&lt;/p&gt;

&lt;p&gt;In those cases, a completely predictable sequence can work against the purpose. The interface should still communicate what is interactive, maintain keyboard and touch access, and protect the user from dead ends. Exploration is not a license to remove orientation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pattern Creates Real Differentiation
&lt;/h3&gt;

&lt;p&gt;A product may need a recognizable interaction of its own, especially when the workflow is central to its identity. The strongest signature interactions become shorthand for the product because they are useful first and distinctive second.&lt;/p&gt;

&lt;p&gt;That order matters. If the only defense is "nobody else does it this way," the design has demonstrated difference, not value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Familiarity Should Usually Win
&lt;/h2&gt;

&lt;p&gt;Familiarity should dominate when the cost of error is high, the task is infrequent, the user is under pressure, or the interaction is only supporting infrastructure.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication and account recovery&lt;/li&gt;
&lt;li&gt;payments, checkout, and subscription changes&lt;/li&gt;
&lt;li&gt;consent and privacy decisions&lt;/li&gt;
&lt;li&gt;destructive operations&lt;/li&gt;
&lt;li&gt;navigation and wayfinding&lt;/li&gt;
&lt;li&gt;form completion and validation&lt;/li&gt;
&lt;li&gt;accessibility controls&lt;/li&gt;
&lt;li&gt;system status and error recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are poor places to demand interpretation. A user who is trying to recover an account or understand a failed payment is not looking for a signature interaction. They need clarity, predictability, and a safe next step.&lt;/p&gt;

&lt;p&gt;Familiarity should also win when novelty is being used to hide weak information architecture. A dramatic menu cannot repair unclear labels. Motion cannot compensate for missing hierarchy. A tutorial cannot permanently solve a control that does not explain itself.&lt;/p&gt;

&lt;p&gt;If the design needs a paragraph of instruction before the first click, the problem may not be insufficient onboarding. The problem may be the interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The FUSE Test
&lt;/h2&gt;

&lt;p&gt;Before shipping an unfamiliar pattern, I use four questions. Together they form the FUSE test: Familiar anchor, User benefit, Safe failure, and Evidence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92187e5vqa4q1hfhmoz0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92187e5vqa4q1hfhmoz0.webp" alt="The FUSE test asks whether an unfamiliar interface has a familiar anchor, a durable user benefit, safe failure and recovery, and evidence from observed behavior." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A novel interaction should not ship on enthusiasm alone. Each part of FUSE reduces a different kind of risk.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Familiar Anchor
&lt;/h3&gt;

&lt;p&gt;What does the user already know that gives them a foothold?&lt;/p&gt;

&lt;p&gt;The anchor may be a recognizable object, label, spatial relationship, or sequence. A new canvas interaction might still use familiar selection handles, undo, zoom controls, and keyboard shortcuts. An AI workflow might still present a visible plan, explicit approval, progress status, and a reviewable result.&lt;/p&gt;

&lt;p&gt;If nothing is familiar, the product is asking the user to learn the domain and the interface at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Benefit
&lt;/h3&gt;

&lt;p&gt;What durable value justifies the learning cost?&lt;/p&gt;

&lt;p&gt;"It feels fresh" is not enough. The change should improve speed, comprehension, control, capability, or expression in a way that matters to the intended user. The benefit should still exist after the novelty wears off.&lt;/p&gt;

&lt;p&gt;This is the question that protects a team from confusing attention with usefulness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe Failure
&lt;/h3&gt;

&lt;p&gt;What happens when the user's prediction is wrong?&lt;/p&gt;

&lt;p&gt;Can they undo the action? Is lost work restored? Does the interface explain the result? Is there a conventional alternative? Does keyboard navigation reach the same outcome? Can a user leave the experiment without being trapped?&lt;/p&gt;

&lt;p&gt;Unfamiliarity increases the chance of incorrect prediction. Recovery is part of the design, not a later edge case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence
&lt;/h3&gt;

&lt;p&gt;What did people actually do?&lt;/p&gt;

&lt;p&gt;Ask participants what they think will happen before they act. Watch for hesitation, repeated scanning, accidental activation, Back behavior, abandonment, and recovery. Compare completion time and error patterns against the conventional alternative. Test with people who did not sit through the design process.&lt;/p&gt;

&lt;p&gt;The most revealing question is often not "Do you like it?" It is "What would you do next, and what do you expect to happen?"&lt;/p&gt;

&lt;p&gt;Preference can reward visual novelty. Prediction reveals whether the interaction has become understandable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Review for Design and Engineering Teams
&lt;/h2&gt;

&lt;p&gt;The familiar-versus-novel decision should not live only in a design critique. It affects implementation, content, accessibility, analytics, and support.&lt;/p&gt;

&lt;p&gt;Before committing to an unfamiliar pattern, I would ask the team to review it from five angles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; What user problem requires a different interaction?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design:&lt;/strong&gt; Which existing mental model are we preserving, extending, or replacing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering:&lt;/strong&gt; What additional state, feedback, fallback, and recovery does the new behavior require?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility:&lt;/strong&gt; Can the same intent be perceived and operated through keyboard, touch, zoom, and assistive technology?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research:&lt;/strong&gt; What observation would cause us to revise or remove the pattern?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This review changes the conversation. Instead of debating whether a concept is exciting or boring, the team discusses where the interaction spends attention and what return it creates.&lt;/p&gt;

&lt;p&gt;It also makes implementation more honest. Novel interactions are rarely just a front-end flourish. They create new states, new explanations, new failure modes, and new testing responsibilities. If the team is unwilling to fund those responsibilities, it has not funded the interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Familiar Core, Novel Edge Principle
&lt;/h2&gt;

&lt;p&gt;The strongest balance I have found is a familiar core with a novel edge.&lt;/p&gt;

&lt;p&gt;The familiar core helps people enter, navigate, understand state, perform common actions, and recover. The novel edge gives the product its particular advantage, expression, or way of working. It is noticeable because the rest of the interface is not competing for the same attention.&lt;/p&gt;

&lt;p&gt;That balance is especially important as interfaces absorb AI-generated suggestions, autonomous actions, spatial tools, richer collaboration, and more adaptive behavior. New capability will require new patterns. It does not require us to discard every convention around those patterns.&lt;/p&gt;

&lt;p&gt;An AI system can propose a new kind of plan while still using familiar approval controls. A design tool can introduce a new manipulation model while preserving undo and visible selection. A research experience can invite exploration while keeping location, progress, and exit clear.&lt;/p&gt;

&lt;p&gt;Innovation works better when users have somewhere stable to stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Familiarity is not the enemy of creativity. It is the infrastructure that lets creativity matter.&lt;/p&gt;

&lt;p&gt;Use conventions for the parts of an interface that should disappear into competence. Use novelty where the product creates a capability, expression, or experience worth learning. Separate visual originality from behavioral change. Protect high-risk tasks with predictable controls. Give unfamiliar patterns a familiar anchor, a durable benefit, safe recovery, and evidence.&lt;/p&gt;

&lt;p&gt;The goal is not to make every interface feel like everything that came before it. The goal is to avoid charging users for differences that do not improve their experience.&lt;/p&gt;

&lt;p&gt;Borrow the instincts people already have. Invent where the product has earned it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Budiu, R. (2024, January 15). &lt;em&gt;&lt;a href="https://www.nngroup.com/articles/recognition-and-recall/" rel="noopener noreferrer"&gt;Memory recognition and recall in user interfaces&lt;/a&gt;.&lt;/em&gt; Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;Chan, M. (2024, January 26). &lt;em&gt;&lt;a href="https://www.nngroup.com/articles/mental-models/" rel="noopener noreferrer"&gt;Mental models and user experience design&lt;/a&gt;.&lt;/em&gt; Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;Budiu, R. (2016, October 30). &lt;em&gt;&lt;a href="https://www.nngroup.com/articles/power-law-learning/" rel="noopener noreferrer"&gt;The power law of learning: Consistency vs. innovation in user interfaces&lt;/a&gt;.&lt;/em&gt; Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;World Wide Web Consortium. (n.d.). &lt;em&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/consistent-navigation.html" rel="noopener noreferrer"&gt;Understanding Success Criterion 3.2.3: Consistent navigation&lt;/a&gt;.&lt;/em&gt; Web Accessibility Initiative.&lt;/p&gt;

&lt;p&gt;Zajonc, R. B. (1968). &lt;a href="https://doi.org/10.1037/h0025848" rel="noopener noreferrer"&gt;Attitudinal effects of mere exposure&lt;/a&gt;. &lt;em&gt;Journal of Personality and Social Psychology, 9&lt;/em&gt;(2, Pt. 2), 1-27.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between familiar and novel interface design?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Familiar interface design uses patterns people already recognize, which lowers learning and prediction costs. Novel interface design introduces a new visual, structural, or behavioral pattern. Novelty is most defensible when it enables a capability or experience that familiar patterns cannot express as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a novelty budget in UI/UX design?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A novelty budget is my way of treating unfamiliarity as a limited product resource. Every new interaction asks users to spend attention and learning effort. A team should reserve that cost for the parts of the product that create durable value and keep commodity tasks predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should a designer break a familiar UI convention?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Break a convention when the existing pattern cannot support the product's core capability, repeated use will repay the learning cost, exploration is part of the task, or the new pattern creates useful differentiation. The change should still include clear feedback, recovery, accessibility, and evidence from user behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does following conventions make every product look the same?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Conventions define expectations about meaning and behavior, not a single visual style. Typography, composition, motion, imagery, pacing, and brand voice can create a distinct experience while navigation, forms, and recovery remain familiar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can teams test an unfamiliar interaction?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask users what they expect before they act, then observe hesitation, errors, recovery, abandonment, and repeated use. Compare the unfamiliar pattern with a conventional alternative. The FUSE test adds four gates: a familiar anchor, a meaningful user benefit, safe failure, and evidence.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>interactiondesign</category>
      <category>design</category>
      <category>userresearch</category>
    </item>
    <item>
      <title>Full Sail, Finished: We Did It</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Sat, 29 Aug 2026 14:31:14 +0000</pubDate>
      <link>https://dev.to/rverwey/full-sail-finished-we-did-it-4ia5</link>
      <guid>https://dev.to/rverwey/full-sail-finished-we-did-it-4ia5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/full-sail-web-development-program-reflection" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Full Sail University's Web Development Bachelor of Science program changed how I build by forcing me to connect software decisions to product outcomes. It was not only a sequence of classes about HTML, CSS, JavaScript, APIs, databases, security, and deployment. It was a long pressure test of whether I could keep improving the way I plan, ship, explain, and maintain real web projects.&lt;/p&gt;

&lt;p&gt;I completed the Web Development Bachelor of Science degree at Full Sail University in July 2026. Along the way, I was named Valedictorian for the Web Development Degree Program, received Full Sail's Advanced Achievement Award, earned four Course Director's Awards, and was recognized by the National Society of Collegiate Scholars for scholarship, leadership, and service.&lt;/p&gt;

&lt;p&gt;Those distinctions matter to me, but not because they make the work complete by themselves. They matter because they reflect the same thing I was trying to build throughout the program: consistent execution, clear thinking, and the ability to turn a requirement into something real.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Earned
&lt;/h2&gt;

&lt;p&gt;The final academic outcome was straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bachelor of Science in Web Development from Full Sail University, awarded July 31, 2026.&lt;/li&gt;
&lt;li&gt;Valedictorian, Web Development Degree Program.&lt;/li&gt;
&lt;li&gt;Advanced Achievement Award, Web Development Degree Program.&lt;/li&gt;
&lt;li&gt;Course Director's Award for Server-Side Languages.&lt;/li&gt;
&lt;li&gt;Course Director's Award for Project and Portfolio III: Web Development.&lt;/li&gt;
&lt;li&gt;Course Director's Award for Project and Portfolio IV: Web Development.&lt;/li&gt;
&lt;li&gt;Course Director's Award for Project and Portfolio V: Web Development.&lt;/li&gt;
&lt;li&gt;National Society of Collegiate Scholars recognition for scholarship, leadership, and service, presented November 2, 2024.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The degree represents completion of the program. Valedictorian represents academic standing. The Advanced Achievement Award means more to me because it is broader than grades. Full Sail describes it as a graduating-class distinction tied to overall effort, dedication, energy, sacrifice, skill, leadership, and program-wide performance.&lt;/p&gt;

&lt;p&gt;The Course Director's Awards sit somewhere different. They are course-level recognition, and I am proud of where they landed. Server-Side Languages validated the backend work that helped push me from frontend implementation into application architecture. Project and Portfolio III, IV, and V validated the larger pattern I was trying to build: take what the program teaches, apply it under deadline pressure, and produce portfolio-grade work that can be explained after the assignment is over.&lt;/p&gt;

&lt;p&gt;I do not see any of these as participation awards. I see them as evidence that I showed up consistently, took the work seriously, and improved in ways that were visible to the instructors evaluating the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Degree and Award Images
&lt;/h2&gt;

&lt;p&gt;The images below are the degree and award records behind the academic honors referenced in this reflection.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="/images/blog/full-sail-bachelor-degree.webp" alt="Full Sail University Bachelor of Science degree awarded to Ryan VerWey for the Web Development Degree Program"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Bachelor of Science, Web Development Degree Program
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-valedictorian-award.webp" alt="Full Sail University Valedictorian certificate awarded to Ryan VerWey for the Web Development Degree Program"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Valedictorian, Web Development Degree Program
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-advanced-achievement-award.webp" alt="Full Sail University Advanced Achievement Award certificate awarded to Ryan VerWey for the Web Development Degree Program"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Advanced Achievement Award, Web Development Degree Program
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-course-director-server-side-languages.webp" alt="Full Sail University Course Director's Award certificate awarded to Ryan VerWey for Server-Side Languages"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Course Director's Award, Server-Side Languages
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-course-director-project-portfolio-3.webp" alt="Full Sail University Course Director's Award certificate awarded to Ryan VerWey for Project and Portfolio III Web Development"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Course Director's Award, Project and Portfolio III
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-course-director-project-portfolio-4.webp" alt="Full Sail University Course Director's Award certificate awarded to Ryan VerWey for Project and Portfolio IV Web Development"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Course Director's Award, Project and Portfolio IV
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-course-director-project-portfolio-5.webp" alt="Full Sail University Course Director's Award certificate awarded to Ryan VerWey for Project and Portfolio V Web Development"&amp;gt;
&amp;lt;figcaption&amp;gt;
  Course Director's Award, Project and Portfolio V
&amp;lt;/figcaption&amp;gt;



&amp;lt;img src="/images/blog/full-sail-nscs-scholarship-leadership-service.webp" alt="National Society of Collegiate Scholars recognition for Ryan VerWey for scholarship leadership and service at Full Sail University"&amp;gt;
&amp;lt;figcaption&amp;gt;
  National Society of Collegiate Scholars recognition
&amp;lt;/figcaption&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Program Was Not Just About Learning Syntax
&lt;/h2&gt;

&lt;p&gt;It is easy to describe a web development degree as a list of technologies. That would be accurate, but incomplete.&lt;/p&gt;

&lt;p&gt;The program did cover technical fundamentals. I worked through HTML structure, CSS layout, JavaScript logic, interface design, application state, backend routes, database models, authentication, deployment, testing, content management, security, and project delivery. Those topics matter. A web developer needs technical range.&lt;/p&gt;

&lt;p&gt;But the more valuable shift was how those topics started connecting.&lt;/p&gt;

&lt;p&gt;An interface is not separate from data modeling. A database decision affects the forms people use. Authentication affects the way a page should explain itself. Search engine optimization affects headings, routing, metadata, and content structure. Security affects input handling, deployment, secrets, and user trust. A portfolio is not just a gallery. It is a product surface that has to be usable, discoverable, credible, and maintainable.&lt;/p&gt;

&lt;p&gt;That was the real growth for me. I stopped seeing web development as a collection of isolated skills and started seeing it as a system of tradeoffs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Coming Into the Program With Real-World Pressure
&lt;/h2&gt;

&lt;p&gt;I did not experience Full Sail in a vacuum.&lt;/p&gt;

&lt;p&gt;I was already bringing a professional background into the program: military experience, geospatial intelligence work, contractor work, business ownership, team coordination, and the kind of operational habits that come from needing work to survive contact with real requirements. That background made the degree feel different.&lt;/p&gt;

&lt;p&gt;When a course asked for a user interface, I was thinking about whether a user could actually make a decision from it. When a course asked for backend logic, I was thinking about where the data came from, who trusted it, and what would happen if it failed. When a course asked for a deployment, I was thinking about environment variables, hosting behavior, search visibility, and whether I could repeat the process later.&lt;/p&gt;

&lt;p&gt;That professional context made school more valuable, not less. The assignments gave structure to things I had already started caring about. They forced me to slow down, name the decisions, and build a cleaner technical foundation underneath instincts I had picked up through work.&lt;/p&gt;

&lt;p&gt;Full Sail did not erase what I already knew. It gave me a more disciplined way to express it as web development.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Personal Cost Was Real
&lt;/h2&gt;

&lt;p&gt;The clean version of this story is easy to write: I earned the degree, finished with honors, built a stronger portfolio, and moved into a clearer developer path.&lt;/p&gt;

&lt;p&gt;That version is true, but it is not the whole truth.&lt;/p&gt;

&lt;p&gt;For most of the program, I was not just a student. I was working at least two jobs, raising children, trying to keep my household moving, and still carving out enough focus to finish assignments at a standard I could be proud of. There were plenty of weeks where school did not fit neatly into life. It had to be wedged into late nights, early mornings, lunch breaks, weekends, and whatever quiet space I could find between work and family responsibilities.&lt;/p&gt;

&lt;p&gt;That kind of pace takes a toll. It cost time. It cost rest. It cost energy I did not always have. At points, it cost pieces of my personal health because I kept choosing the long-term goal over short-term comfort. I do not want to romanticize that. Sacrifice sounds cleaner after the fact than it feels while you are living through it.&lt;/p&gt;

&lt;p&gt;There were days when the work felt heavier than the assignment in front of me. A project deadline was not just a project deadline. It was stacked on top of work pressure, parenting, finances, fatigue, and the constant mental load of trying not to drop anything important. Finishing the degree required more than technical ability. It required endurance.&lt;/p&gt;

&lt;p&gt;That is part of why the awards matter to me. They are not only academic decorations. They represent a period where I kept showing up while carrying a lot at once. They represent nights where I could have done the minimum and chose to push for better. They represent the discipline it took to build a new professional direction without being able to pause the rest of life.&lt;/p&gt;

&lt;p&gt;In the end, it was worth it because the program gave me more than a credential. It gave me the start of a new life and a real career path as a developer. It gave shape to work I already cared about. It gave me a portfolio, a stronger technical foundation, a clearer professional identity, and proof that I could keep building even when the circumstances were not easy.&lt;/p&gt;

&lt;p&gt;That is the part I will remember most.&lt;/p&gt;




&lt;h2&gt;
  
  
  Graduation Day
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdswcfctct277evwc4mzt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdswcfctct277evwc4mzt.webp" alt="Ryan VerWey with family at Full Sail University graduation after completing the Web Development Bachelor of Science program" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graduation day made the sacrifice feel visible. The degree was mine, but getting there was never only an individual effort. My family lived through the long schedule with me: the work hours, the school deadlines, the late nights, the stress, and the constant balancing act it took to keep everything moving.&lt;/p&gt;

&lt;p&gt;That picture matters because it shows what the program was really attached to. It was not just a career move on paper. It was a decision to build a different future while still being present for the people depending on me. That made the process harder, but it also made the outcome mean more.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Early Courses Built the Mental Model
&lt;/h2&gt;

&lt;p&gt;The first stretch of the program mattered because it established the mental model underneath everything else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ryanverwey.dev/blog/first-steps-into-webdev-fullsail" rel="noopener noreferrer"&gt;Introduction to Web Architecture&lt;/a&gt; made the browser feel less like a black box. HTML and CSS became structural tools instead of files that happened to make a page appear. &lt;a href="https://www.ryanverwey.dev/blog/interfaces-and-usability-fullsail" rel="noopener noreferrer"&gt;Interfaces and Usability&lt;/a&gt; pushed that further by making design feel less subjective. Layout, hierarchy, spacing, contrast, and responsive behavior became part of how a product communicates.&lt;/p&gt;

&lt;p&gt;The early JavaScript courses were where logic became more practical. Conditionals, loops, arrays, objects, functions, and classes are easy to describe academically. They become more meaningful when they start controlling actual behavior in the browser. The program gave me repetition there, and repetition matters. You do not become useful with JavaScript by watching someone else explain it. You become useful by writing it, breaking it, debugging it, and understanding why it broke.&lt;/p&gt;

&lt;p&gt;Those early courses did not produce my most advanced projects, but they made the later projects possible. They built the habits that make larger systems easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Portfolio Courses Changed the Standard
&lt;/h2&gt;

&lt;p&gt;The Project and Portfolio courses were the backbone of the program for me.&lt;/p&gt;

&lt;p&gt;Those courses changed the standard from "complete the assignment" to "produce something you can stand behind." That distinction matters. A normal class assignment can disappear after grading. A portfolio project has to explain what you can do.&lt;/p&gt;

&lt;p&gt;Project and Portfolio I helped pull the early fundamentals together. &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-2-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio II&lt;/a&gt; pushed deeper into React, API integration, data, and application behavior. &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-3-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio III&lt;/a&gt; was where a fuller application started to emerge through authentication, profiles, blog features, project boards, and sprint planning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-4-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio IV&lt;/a&gt; raised the bar again by requiring more serious attention to access control, audit trails, cloud services, deployment, discovery, and postmortem work. &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-5-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio V&lt;/a&gt; and &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-6-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio VI&lt;/a&gt; became the launch path for Our Heritage Kitchen, moving it from product idea to beta planning, launch planning, search engine optimization, analytics, maintenance documentation, and handoff.&lt;/p&gt;

&lt;p&gt;That sequence changed my expectations for my own work. By the end, I was not satisfied with a demo that technically loaded. I wanted clean user flows, readable content, meaningful metadata, deployment clarity, version history, maintainable structure, and enough written context that another person could understand what I built without a private walkthrough.&lt;/p&gt;

&lt;p&gt;That mindset now shows up across my &lt;a href="https://www.ryanverwey.dev/projects" rel="noopener noreferrer"&gt;project portfolio&lt;/a&gt;, especially in the way I present live products, case details, technical stacks, and business context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backend Work Made Me Think in Systems
&lt;/h2&gt;

&lt;p&gt;The backend courses were important because they moved me past interface-only thinking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ryanverwey.dev/blog/server-side-languages-fullsail" rel="noopener noreferrer"&gt;Server-Side Languages&lt;/a&gt; was one of the courses that stuck with me most. It helped make API design, routing, testing, MongoDB, Docker, and Node.js feel like part of ordinary product development. That Course Director's Award meant a lot because it marked a point where backend work was no longer just something sitting behind the interface. It became a core part of how I thought about building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ryanverwey.dev/blog/advanced-server-side-languages-fullsail" rel="noopener noreferrer"&gt;Advanced Server-Side Languages&lt;/a&gt; expanded that by pushing cleaner API architecture, TypeScript patterns, Express structure, WebSockets, and better service thinking. &lt;a href="https://www.ryanverwey.dev/blog/database-systems-fullsail" rel="noopener noreferrer"&gt;Database Systems&lt;/a&gt; added another layer by making persistence, backups, permissions, and database operations more explicit.&lt;/p&gt;

&lt;p&gt;The practical lesson was simple: full-stack development is not frontend plus backend as separate piles of work. It is the ability to design the boundary between them.&lt;/p&gt;

&lt;p&gt;That boundary affects performance, validation, security, user experience, analytics, and maintainability. It affects how clearly a feature can be tested. It affects whether the next developer can understand what the system is doing. It affects whether the user sees a coherent product or a collection of technical compromises.&lt;/p&gt;

&lt;p&gt;Those courses strengthened the systems side of my work, and I still feel that influence when building tools, dashboards, design systems, and business-facing applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Changed How I Look at Ordinary Code
&lt;/h2&gt;

&lt;p&gt;The security-related coursework helped me become more skeptical in a useful way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ryanverwey.dev/blog/app-integration-security-fullsail" rel="noopener noreferrer"&gt;Application Integration and Security&lt;/a&gt; introduced security pressure in a broader application context. &lt;a href="https://www.ryanverwey.dev/blog/secure-application-development-fullsail" rel="noopener noreferrer"&gt;Secure Application Development&lt;/a&gt; made it more direct: browser trust, cross-site request forgery, command injection, secrets management, JSON Web Tokens, deployment risk, and the responsibilities that continue after code leaves the local machine.&lt;/p&gt;

&lt;p&gt;I do not market myself as a dedicated cybersecurity specialist. That is not the point. The point is that a full-stack developer cannot treat security as someone else's future cleanup task. The developer is often the person deciding how input is handled, where secrets live, how authentication works, how routes change data, how tokens move, and how deployment environments are configured.&lt;/p&gt;

&lt;p&gt;That course changed the way I look at normal development decisions. When I see a request parameter, I think about validation. When I see a key or token, I think about storage and rotation. When I see a form that changes state, I think about intent. When I see a production deployment, I think about what the system is trusting.&lt;/p&gt;

&lt;p&gt;That is not glamorous, but it is professional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content, Discovery, and Presentation Became Part of the Work
&lt;/h2&gt;

&lt;p&gt;One of the more useful lessons from the later program was that software does not speak for itself.&lt;/p&gt;

&lt;p&gt;Developers sometimes want the code to be enough. It rarely is. A project needs to be explained. It needs a purpose, an audience, a clear interface, useful metadata, and enough documentation that someone can evaluate it quickly.&lt;/p&gt;

&lt;p&gt;That lesson has shaped this portfolio heavily. I care about search engine optimization, answer engine optimization, generative engine optimization, structured content, internal links, evidence, page hierarchy, and readable project summaries because discoverability is part of the work. If a recruiter, business owner, hiring manager, or AI search system cannot understand what a project demonstrates, then the project is not doing its job as a portfolio artifact.&lt;/p&gt;

&lt;p&gt;My Full Sail retrospectives became part of that process. Each article helped package a piece of the program into something searchable and explainable. The goal was not to turn schoolwork into filler content. The goal was to document growth in a way that connects coursework to real capability.&lt;/p&gt;

&lt;p&gt;That is why the &lt;a href="https://www.ryanverwey.dev/blog/full-sail" rel="noopener noreferrer"&gt;Full Sail University learning journal&lt;/a&gt; exists. It gives the program a narrative arc instead of leaving it as a line item on a resume.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Work Started Turning Into Real Products
&lt;/h2&gt;

&lt;p&gt;By the end of the program, the work was not confined to class deliverables.&lt;/p&gt;

&lt;p&gt;I was building and improving my own portfolio as a living system. I was building project pages that explain technical choices instead of just showing screenshots. I was writing articles that package decisions, constraints, and lessons learned. I was building tools through Echo Effect, experimenting with AI-assisted portfolio discovery, and turning design system work into something reusable.&lt;/p&gt;

&lt;p&gt;That matters because professional growth is not only measured by the complexity of the code. It is measured by whether the code becomes useful.&lt;/p&gt;

&lt;p&gt;Our Heritage Kitchen became a full product case study. CorvaUI became a design system direction with documentation and framework demos. Optimize became a website audit product focused on search, answer engine optimization, generative engine optimization, crawler access, accessibility, and prioritized repair planning. Rupert became a grounded AI assistant for helping visitors understand my portfolio. These projects are not all the same kind of work, but they share the same pattern: interface, logic, content, data, deployment, and communication all have to support the same outcome.&lt;/p&gt;

&lt;p&gt;That is what Full Sail helped reinforce. Build the thing, but also make it understandable. Make it work, but also make it maintainable. Finish the assignment, but turn the learning into a reusable professional asset.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Valedictorian Means to Me
&lt;/h2&gt;

&lt;p&gt;Valedictorian is easy to explain from the outside: highest academic standing in the program.&lt;/p&gt;

&lt;p&gt;From the inside, it feels more personal than that. It represents consistency. It means I kept the work moving across a long program while also carrying real life, real professional pressure, and a growing set of projects outside the classroom.&lt;/p&gt;

&lt;p&gt;I am proud of the academic result, but I am more proud of the pattern behind it. Showing up every week matters. Reading the requirements carefully matters. Asking whether the work is actually good matters. Taking feedback seriously matters. Revising things that technically pass but do not meet your own standard matters.&lt;/p&gt;

&lt;p&gt;That is the part I want people to understand when they see the word Valedictorian on my profile. It is not just a grade-point detail. It is shorthand for sustained execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Advanced Achievement Award Means to Me
&lt;/h2&gt;

&lt;p&gt;The Advanced Achievement Award means something different.&lt;/p&gt;

&lt;p&gt;Valedictorian is academic. The Advanced Achievement Award is broader. It recognizes the kind of student Full Sail views as standing out across the graduating class through performance, effort, leadership, skill, sacrifice, and the overall way they moved through the program.&lt;/p&gt;

&lt;p&gt;That matters because software work is rarely judged on one dimension. Good grades are valuable, but professional trust comes from more than grades. It comes from communication, ownership, judgment, follow-through, and the ability to keep making progress when a problem is messy.&lt;/p&gt;

&lt;p&gt;That is why this award hit differently for me. It felt like recognition of the whole pattern, not just the transcript. The late nights, the rewrites, the overbuilt first drafts, the cleaner second drafts, the projects that had to be shipped, the feedback I had to absorb, and the effort to keep raising the bar all counted.&lt;/p&gt;

&lt;p&gt;Receiving that award alongside Valedictorian and multiple Course Director's Awards made the end of the program feel complete in a way I did not expect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Course Director's Awards Were Signals Along the Way
&lt;/h2&gt;

&lt;p&gt;The four Course Director's Awards gave me mile markers.&lt;/p&gt;

&lt;p&gt;Server-Side Languages showed that the backend side of the program had clicked. That course required more than making buttons respond. It required routes, requests, responses, data, tests, and a clearer understanding of how applications behave away from the browser.&lt;/p&gt;

&lt;p&gt;Project and Portfolio III showed that I could pull multiple disciplines into a real full-stack project. It was not just a technical checkpoint. It was a delivery checkpoint.&lt;/p&gt;

&lt;p&gt;Project and Portfolio IV showed that I could revise and mature a project through access control, audit trails, cloud services, deployment thinking, and postmortem reflection.&lt;/p&gt;

&lt;p&gt;Project and Portfolio V showed that I could move a larger product idea toward beta with business viability, metrics, compliance, security posture, versioning, release planning, and a stronger sense of product ownership.&lt;/p&gt;

&lt;p&gt;Those awards do not replace the work. They point back to it. They mark moments where the work was strong enough to stand out.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Full Sail Contributed to My Professional Growth
&lt;/h2&gt;

&lt;p&gt;The biggest professional change was not that I learned a few more frameworks.&lt;/p&gt;

&lt;p&gt;The biggest change was that I became more deliberate.&lt;/p&gt;

&lt;p&gt;I became more deliberate about requirements. I started reading the assignment, project brief, or business need as a product contract instead of a checklist. What does the user need to do? What does the system need to prove? What does the page need to communicate? What will be hard to maintain later?&lt;/p&gt;

&lt;p&gt;I became more deliberate about interfaces. A component is not just a styled block. It is a decision about hierarchy, state, accessibility, responsiveness, and user confidence.&lt;/p&gt;

&lt;p&gt;I became more deliberate about backend logic. An API is not just a route. It is a boundary where trust, validation, persistence, and errors have to be handled clearly.&lt;/p&gt;

&lt;p&gt;I became more deliberate about deployment. A deployed application is not finished if the environment is fragile, the metadata is weak, the handoff is unclear, or nobody can tell what changed.&lt;/p&gt;

&lt;p&gt;I became more deliberate about writing. The ability to explain technical work is part of the work. A project without context is harder to evaluate, harder to trust, and easier to misunderstand.&lt;/p&gt;

&lt;p&gt;That is the professional growth I value most. Full Sail gave me structure, deadlines, repetition, feedback, and a progression that pushed those habits into place.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Would Tell Someone Starting the Program
&lt;/h2&gt;

&lt;p&gt;If I were talking to someone starting the Full Sail Web Development program, I would tell them not to treat the degree like a checklist.&lt;/p&gt;

&lt;p&gt;The assignments matter, but the artifacts matter more. Every course can become something useful if you treat it as part of a larger portfolio. Write down what you learned. Keep the work. Improve it after the grade. Connect the course to a real project. Ask what the assignment is actually trying to teach, not just what needs to be submitted by Sunday night.&lt;/p&gt;

&lt;p&gt;I would also tell them to build more than the minimum. Not recklessly, and not at the cost of missing requirements, but enough that the work becomes yours. The best learning I got from the program happened when I stopped asking "what is enough to pass?" and started asking "what would make this worth showing?"&lt;/p&gt;

&lt;p&gt;That question changed everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Take Forward
&lt;/h2&gt;

&lt;p&gt;I take forward a clearer identity as a developer.&lt;/p&gt;

&lt;p&gt;I build full-stack products, design systems, AI-assisted tools, search-ready content, and business websites that need to work for real people. I care about interfaces, but I also care about the system behind them. I care about code, but I also care about whether the product can be found, understood, measured, and maintained.&lt;/p&gt;

&lt;p&gt;Full Sail did not create that identity from nothing. It sharpened it. It gave me a structured path to prove it over and over again, course by course, project by project.&lt;/p&gt;

&lt;p&gt;The final degree, Valedictorian honor, Advanced Achievement Award, Course Director's Awards, and National Society recognition are important because they document the outcome. But the more important result is practical: I left the program with a stronger standard for my own work.&lt;/p&gt;

&lt;p&gt;That standard is what I am carrying forward.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Few Acknowledgements
&lt;/h2&gt;

&lt;p&gt;I owe Rachel more than a quick thank-you line can cover.&lt;/p&gt;

&lt;p&gt;She carried the reality of this program with me, not just the idea of it. Every late night, every overloaded week, every deadline that landed on top of work and family life affected her too. She had to live with the schedule, the stress, the distracted evenings, the times I was physically home but mentally buried in code, documentation, deployments, or school requirements. Finishing this degree took effort from me, but it also took patience, grace, and sacrifice from her.&lt;/p&gt;

&lt;p&gt;Rachel believed in the version of my life I was trying to build even when the day-to-day version of it was not clean or easy. She encouraged me when I was exhausted, gave me room when I needed to focus, and kept standing beside me through a process that asked a lot from our family. This degree is part of my professional story, but she is part of why I was able to finish it.&lt;/p&gt;

&lt;p&gt;To Trinity and Alex, I hope one day this makes sense in a way that goes beyond seeing Dad on a laptop all the time.&lt;/p&gt;

&lt;p&gt;There were nights, weekends, and ordinary family moments where school had to take space. I know that mattered. I know there were times when I had to ask for quiet, steal time, or push through one more assignment instead of being as present as I wanted to be. That is one of the hardest parts to write honestly because the goal was always for our family, but the work sometimes pulled me away from the very people I was doing it for.&lt;/p&gt;

&lt;p&gt;I hope what you take from this is not that work should consume your life. I hope you see that hard things can be finished, that starting over professionally is possible, and that building a better future sometimes requires a season of sacrifice. I wanted you to see me finish. I wanted you to see that learning does not stop when you become an adult, a parent, or a provider. I wanted you to have proof that persistence can turn into something real.&lt;/p&gt;

&lt;p&gt;To my friends and family, thank you for the encouragement, the check-ins, the patience, and the belief.&lt;/p&gt;

&lt;p&gt;Support does not always look dramatic from the outside. Sometimes it is a message at the right time, a conversation that helps you reset, someone listening while you are frustrated, or someone reminding you that the work is worth finishing. Those moments mattered more than I probably said while I was in the middle of it. When life was crowded and the finish line still felt far away, encouragement helped keep the goal visible.&lt;/p&gt;

&lt;p&gt;I also want to thank my Full Sail instructors for the mentorship, feedback, pressure, and structure they brought into the program.&lt;/p&gt;

&lt;p&gt;The best instructors did more than grade submissions. They made the work feel connected to a professional standard. They pushed clarity. They rewarded effort, but they also expected execution. They treated projects like opportunities to think harder, communicate better, and build with more intent. Their feedback helped me see where an assignment could become a stronger product, where a technical choice needed a better explanation, and where "working" was not the same thing as being finished.&lt;/p&gt;

&lt;p&gt;That mentorship mattered. It helped me turn coursework into portfolio work, portfolio work into professional direction, and professional direction into a stronger sense of what I want to build next.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What degree did Ryan VerWey earn from Full Sail University?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I earned a Bachelor of Science in Web Development from Full Sail University. The degree was awarded on July 31, 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What academic honors did Ryan VerWey receive at Full Sail University?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was named Valedictorian for the Web Development Degree Program, received the Advanced Achievement Award, and earned Course Director's Awards in Server-Side Languages, Project and Portfolio III: Web Development, Project and Portfolio IV: Web Development, and Project and Portfolio V: Web Development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Full Sail's Advanced Achievement Award?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Advanced Achievement Award is a graduating-class distinction that recognizes a standout student across the full program. It is broader than grade-point average alone and reflects effort, dedication, energy, sacrifice, skill, leadership, and overall program performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Course Director's Award at Full Sail University?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Course Director's Award is course-level recognition from Full Sail faculty for standout performance in a specific course. In my case, those awards came from Server-Side Languages and three Project and Portfolio courses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How did Full Sail contribute to Ryan VerWey's professional growth?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Full Sail helped connect technical skills to professional delivery. The program strengthened my work across frontend interfaces, backend logic, databases, security, deployment, testing, documentation, product planning, portfolio presentation, and search-ready content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does this reflection connect to the rest of Ryan VerWey's portfolio?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This reflection connects the Full Sail program to the broader portfolio. The course retrospectives live in my &lt;a href="https://www.ryanverwey.dev/blog/full-sail" rel="noopener noreferrer"&gt;Full Sail learning journal&lt;/a&gt;, and the applied project work is collected on my &lt;a href="https://www.ryanverwey.dev/projects" rel="noopener noreferrer"&gt;projects page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;p&gt;This post closes out my &lt;a href="https://www.ryanverwey.dev/blog/full-sail" rel="noopener noreferrer"&gt;Full Sail University learning journal&lt;/a&gt;. For course-level detail, start with &lt;a href="https://www.ryanverwey.dev/blog/first-steps-into-webdev-fullsail" rel="noopener noreferrer"&gt;First Steps into Web Development&lt;/a&gt;, then follow the sequence through &lt;a href="https://www.ryanverwey.dev/blog/server-side-languages-fullsail" rel="noopener noreferrer"&gt;Server-Side Languages&lt;/a&gt;, &lt;a href="https://www.ryanverwey.dev/blog/secure-application-development-fullsail" rel="noopener noreferrer"&gt;Secure Application Development&lt;/a&gt;, &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-5-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio V&lt;/a&gt;, and &lt;a href="https://www.ryanverwey.dev/blog/project-portfolio-6-fullsail" rel="noopener noreferrer"&gt;Project and Portfolio VI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>education</category>
      <category>webdev</category>
      <category>portfolio</category>
      <category>professionalgrowth</category>
    </item>
    <item>
      <title>Turning GitHub Language Stats Into a Profile Snapshot</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:44:39 +0000</pubDate>
      <link>https://dev.to/rverwey/turning-github-language-stats-into-a-profile-snapshot-2hlp</link>
      <guid>https://dev.to/rverwey/turning-github-language-stats-into-a-profile-snapshot-2hlp</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/github-profile-language-metrics-python-action" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/RyanVerWey/profile-language-metrics" rel="noopener noreferrer"&gt;profile-language-metrics&lt;/a&gt; as a reusable GitHub Action that generates a clean language snapshot for a GitHub profile README. It is written in Python, runs on GitHub Actions, scans active repositories, estimates non-empty source lines by language, and writes a polished SVG that can be embedded directly into a profile page.&lt;/p&gt;

&lt;p&gt;The short version: it is a vanity metric, but it is an honest one. It does not pretend to measure engineering value, productivity, or skill level by itself. It gives developers a lightweight way to show the shape of their public work, and it gives me another practical Python automation project that solves a real presentation problem.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finw7ld830qaflw6ze5ag.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finw7ld830qaflw6ze5ag.webp" alt="Cover graphic for profile-language-metrics showing a Python-built GitHub Action release and current public language snapshot" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;GitHub profile READMEs are weirdly useful.&lt;/p&gt;

&lt;p&gt;They are part portfolio, part landing page, part proof-of-work surface. A good profile does not need to be loud, but it should give someone a fast read on what you build, what tools you use, and whether your work is active.&lt;/p&gt;

&lt;p&gt;The problem is that most profile widgets either depend on a third-party service, show generic GitHub stats that everyone has already seen, or turn into visual noise. I wanted something smaller and more specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a card that shows the language mix across my repos&lt;/li&gt;
&lt;li&gt;a scheduled workflow that keeps it updated&lt;/li&gt;
&lt;li&gt;no hosted dashboard&lt;/li&gt;
&lt;li&gt;no package install step&lt;/li&gt;
&lt;li&gt;no private repository names exposed&lt;/li&gt;
&lt;li&gt;a result that looks good embedded in Markdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made Python the right tool for the job. The project needed file walking, API calls, subprocess control, counters, text escaping, and SVG generation. None of that required a framework. It needed boring reliability more than a big dependency tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Action Generates
&lt;/h2&gt;

&lt;p&gt;The current public run against my GitHub profile found 40,292 estimated non-empty source lines across 17 public active repositories. Python shows up with 3,171 lines in that snapshot, alongside TypeScript, JavaScript, CSS, HTML, Astro, Svelte, Vue, YAML, MDX, Shell, Batchfile, and TOML.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci6dwgnchtak9r1kjb3r.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci6dwgnchtak9r1kjb3r.webp" alt="Generated profile-language-metrics result card showing language totals across Ryan VerWey public GitHub repositories" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output is intentionally simple: one SVG card with a total line count, active repository count, stacked language distribution, and per-language rows. It is built for a README, not a business intelligence dashboard.&lt;/p&gt;

&lt;p&gt;That distinction matters. Line counts can be useful context, but they are not proof of seniority or quality. The value here is presentation: it turns scattered repository activity into a compact snapshot someone can understand in a few seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The action follows a predictable pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch eligible repositories from the GitHub API.&lt;/li&gt;
&lt;li&gt;Create a temporary, shallow, read-only checkout of each active repository inside the GitHub Actions runner.&lt;/li&gt;
&lt;li&gt;Walk source files and map extensions to languages.&lt;/li&gt;
&lt;li&gt;Count non-empty source lines.&lt;/li&gt;
&lt;li&gt;Render the aggregated result into an SVG.&lt;/li&gt;
&lt;li&gt;Let the profile workflow commit the generated file.&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjz951hypk3s9jjot95lf.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjz951hypk3s9jjot95lf.webp" alt="Workflow graphic showing profile-language-metrics fetching repos, using temporary GitHub Actions checkouts, counting source, and rendering SVG output" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That clone step deserves plain-English context. The action is not sending your repository to a third-party server, creating a permanent mirror, or changing your project files. It runs inside your own GitHub Actions job, performs a depth-1 checkout in a temporary runner workspace, reads the files for counting, and lets that workspace disappear when the job finishes. The only file the example workflow writes back is the generated &lt;code&gt;assets/language-metrics.svg&lt;/code&gt; in the profile repository where you installed the action.&lt;/p&gt;

&lt;p&gt;The repo filtering is as important as the counting. The action skips forks, archived repos, disabled repos, profile repos, dependency folders, build output, lockfiles, minified files, and binaries. Without those exclusions, the output gets polluted fast.&lt;/p&gt;

&lt;p&gt;The counting logic is deliberately conservative. It estimates non-empty source lines in recognized source files. It does not try to parse abstract syntax trees, classify generated code perfectly, or become a compliance-grade code inventory tool.&lt;/p&gt;

&lt;p&gt;That restraint is part of the design. For a profile README, I would rather have a clear, repeatable estimate than a complex analysis system that feels more official than it really is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Python Was the Right Fit
&lt;/h2&gt;

&lt;p&gt;This is the kind of Python I like writing: practical automation that sits between services, files, and developer workflow.&lt;/p&gt;

&lt;p&gt;The script has to do several jobs cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call GitHub's API with optional authentication&lt;/li&gt;
&lt;li&gt;handle public-only and private-inclusive modes&lt;/li&gt;
&lt;li&gt;create temporary repository checkouts without mutating source repositories or leaking private paths into logs&lt;/li&gt;
&lt;li&gt;skip binary and generated files&lt;/li&gt;
&lt;li&gt;count text safely with encoding fallbacks&lt;/li&gt;
&lt;li&gt;aggregate language totals with &lt;code&gt;Counter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;escape SVG text correctly&lt;/li&gt;
&lt;li&gt;write an output file that works in GitHub Markdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation stays dependency-free. It uses Python's standard library plus Git on the runner. That makes the action easier to trust because the user does not have to wait on &lt;code&gt;npm install&lt;/code&gt;, audit a package tree, or wonder why a profile utility needs a heavy runtime.&lt;/p&gt;

&lt;p&gt;Here is the kind of workflow a user can drop into a profile repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate language metrics&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;37&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RyanVerWey/profile-language-metrics@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github_user_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;output_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets/language-metrics.svg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the user experience I wanted: configure it once, run it manually when you want the first image, then let the scheduled workflow keep it current.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Privacy Model
&lt;/h2&gt;

&lt;p&gt;Private repository support needed a clear boundary.&lt;/p&gt;

&lt;p&gt;The action can include private repositories when the caller supplies a token with the right access, but the generated SVG only shows aggregate totals. It does not write private repository names, URLs, or per-repo breakdowns into the image.&lt;/p&gt;

&lt;p&gt;That tradeoff is intentional. A developer might want their private work represented in the overall language mix, but a profile README is still public. Aggregates are useful. Private project disclosure is not.&lt;/p&gt;

&lt;p&gt;I also suppress clone command output when a token is present and disable Git Large File Storage smudge during clone. That keeps logs cleaner and avoids pulling large private assets that do not belong in a lightweight metrics run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Shows About My Python Work
&lt;/h2&gt;

&lt;p&gt;This project is not Python for Python's sake. It is Python used where Python makes the product simpler.&lt;/p&gt;

&lt;p&gt;The code has to be comfortable with API boundaries, local file systems, subprocesses, temporary workspaces, string rendering, escaping, and operational edge cases. It also has to be packaged so another developer can use it without reading the entire implementation first.&lt;/p&gt;

&lt;p&gt;That is the part I care about most: turning a script into a reusable tool.&lt;/p&gt;

&lt;p&gt;A one-off script would have been easy. A release needs more discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clear inputs&lt;/li&gt;
&lt;li&gt;documented outputs&lt;/li&gt;
&lt;li&gt;safe defaults&lt;/li&gt;
&lt;li&gt;exclusion rules&lt;/li&gt;
&lt;li&gt;read-only private token guidance&lt;/li&gt;
&lt;li&gt;a license&lt;/li&gt;
&lt;li&gt;attribution notes&lt;/li&gt;
&lt;li&gt;a README that gets someone from zero to embedded SVG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the difference between "I wrote some Python" and "I shipped a Python-based developer tool people can use."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Improve Next
&lt;/h2&gt;

&lt;p&gt;The release is useful now, but there are a few improvements I would consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding more language extensions as edge cases appear&lt;/li&gt;
&lt;li&gt;improving the stacked bar renderer for very small language segments&lt;/li&gt;
&lt;li&gt;offering a compact card variant for narrower profile layouts&lt;/li&gt;
&lt;li&gt;adding a JSON output for people who want to build their own visualization&lt;/li&gt;
&lt;li&gt;documenting a few example profile README layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would still keep the core small. This kind of project gets worse if it tries to become a full analytics platform. The point is a clean profile snapshot, not an enterprise code intelligence system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The project is public here: &lt;a href="https://github.com/RyanVerWey/profile-language-metrics" rel="noopener noreferrer"&gt;RyanVerWey/profile-language-metrics&lt;/a&gt;. If you like the release or end up using it in your own profile README, star the repo. That signal helps the project reach other developers who want a clean GitHub vanity snapshot without depending on a hosted badge service.&lt;/p&gt;

&lt;p&gt;If you want a profile README card that shows your language mix without relying on a hosted badge service, add the action to your profile repository and let GitHub Actions commit the SVG. It is small, readable, MIT licensed, and built to be easy to understand.&lt;/p&gt;

&lt;p&gt;For more context on the kind of developer work I build and publish, see my &lt;a href="https://www.ryanverwey.dev/projects" rel="noopener noreferrer"&gt;projects&lt;/a&gt; and &lt;a href="https://www.ryanverwey.dev/about" rel="noopener noreferrer"&gt;about page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is profile-language-metrics?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;profile-language-metrics is a reusable GitHub Action that generates a profile-ready SVG showing aggregate source-line estimates by programming language across active repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this a serious engineering metric?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. It is a useful profile visualization, not a measurement of engineering quality, productivity, or business impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did you build it in Python?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python fit the job: API requests, file walking, subprocess calls, counters, text escaping, and SVG rendering without pulling in a package install step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it include private repositories?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, if the caller supplies a token with access. Private repositories are shown only as aggregate totals, not as names, URLs, or per-repository details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should use it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers who want a clean GitHub profile README language snapshot that updates on a schedule and stays under their own repository control.&lt;/p&gt;

</description>
      <category>python</category>
      <category>githubactions</category>
      <category>automation</category>
      <category>svg</category>
    </item>
    <item>
      <title>Publish First, Syndicate Later: Automating Dev.to Without Losing SEO Credit</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Fri, 21 Aug 2026 14:43:05 +0000</pubDate>
      <link>https://dev.to/rverwey/publish-first-syndicate-later-automating-devto-without-losing-seo-credit-4akm</link>
      <guid>https://dev.to/rverwey/publish-first-syndicate-later-automating-devto-without-losing-seo-credit-4akm</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.ryanverwey.dev/blog/automate-nextjs-blog-devto-canonical-delay" rel="noopener noreferrer"&gt;ryanverwey.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you publish articles on a Next.js portfolio or agency website, Dev.to can be a useful syndication channel. You get distribution in a developer community, another place for people to discover your work, and a canonical backlink pointing to the original article on your own domain.&lt;/p&gt;

&lt;p&gt;The important part is order.&lt;/p&gt;

&lt;p&gt;Publish on your website first. Give search engines time to crawl your original page. Then publish the Dev.to copy with a canonical URL that points back to your site. That is how you use syndication for reach without teaching Google that the copy is the source of truth.&lt;/p&gt;

&lt;p&gt;This guide walks through a practical setup: a Next.js blog, an RSS feed or local Markdown files, a Dev.to API key stored as a GitHub secret, a daily GitHub Actions workflow, and a script that waits three days before posting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; Publish the original post on your own domain, wait three days, then post the Dev.to copy with &lt;code&gt;canonical_url&lt;/code&gt; set to your original URL. Include the cover image, convert body images to absolute URLs, and send no more than four Dev.to tags.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Syndicate to Dev.to at All?
&lt;/h2&gt;

&lt;p&gt;Your portfolio blog is the source of truth. That is where you own the domain, the analytics, the internal links, the service pages, the calls to action, and the long-term SEO value.&lt;/p&gt;

&lt;p&gt;Dev.to is different. It is a discovery channel.&lt;/p&gt;

&lt;p&gt;When you syndicate correctly, Dev.to can help with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Getting technical articles in front of readers who may never find your personal site first.&lt;/li&gt;
&lt;li&gt;Creating a clean attribution path from a high-authority community platform back to your original post.&lt;/li&gt;
&lt;li&gt;Building repeatable distribution without manually copying every article.&lt;/li&gt;
&lt;li&gt;Keeping your writing workflow centered on your own website, not on rented platforms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The danger is duplicate content confusion. If the same article appears on your site and on Dev.to at the same time, Google has to decide which URL represents the original. Google recommends using canonical links in the HTML head and using absolute URLs for canonical annotations. DEV also documents a canonical URL field for imported or manually created posts, specifically so the original source can keep SEO credit.&lt;/p&gt;

&lt;p&gt;That is why the delay matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Three-Day Delay Does
&lt;/h2&gt;

&lt;p&gt;A three-day delay is not a magic ranking switch. It is a practical buffer.&lt;/p&gt;

&lt;p&gt;When a new article goes live on your portfolio, you want search engines to see your domain first. The delay gives you time to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the post.&lt;/li&gt;
&lt;li&gt;Confirm the article URL returns &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm the canonical tag on your site points to itself.&lt;/li&gt;
&lt;li&gt;Let your sitemap, RSS feed, and internal links expose the original before Dev.to sees the copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that delay, the Dev.to version should point back to the original with &lt;code&gt;canonical_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of the Dev.to article as a syndicated copy with a signpost. The signpost says, "This is useful here, but the original lives over there."&lt;/p&gt;

&lt;p&gt;For high-priority posts, you can also request indexing in Google Search Console. Treat that as an acceleration option, not as a required step in the recurring automation.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcy8ru7zm2xeix8326dh.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcy8ru7zm2xeix8326dh.webp" alt="Canonical syndication timeline showing original publication, sitemap discovery, crawler review, a three-day delay window, and the syndicated copy pointing back to the source article" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Are Building
&lt;/h2&gt;

&lt;p&gt;The automation has six parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Next.js blog source, usually Markdown or MDX files.&lt;/li&gt;
&lt;li&gt;A canonical URL for every article on your own domain.&lt;/li&gt;
&lt;li&gt;An RSS feed or script that can read each article's title, summary, body, date, image, and tags.&lt;/li&gt;
&lt;li&gt;A Dev.to API key stored outside your repo.&lt;/li&gt;
&lt;li&gt;A sync script that publishes only eligible posts.&lt;/li&gt;
&lt;li&gt;A scheduled GitHub Actions workflow that runs daily.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workflow should do this every time it runs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read all local blog posts.&lt;/li&gt;
&lt;li&gt;Skip posts newer than three days.&lt;/li&gt;
&lt;li&gt;Build the original URL from the post slug.&lt;/li&gt;
&lt;li&gt;Check Dev.to for an existing article with that canonical URL.&lt;/li&gt;
&lt;li&gt;Convert MDX or HTML-only content into Dev.to-friendly Markdown.&lt;/li&gt;
&lt;li&gt;Make images absolute so they work away from your website.&lt;/li&gt;
&lt;li&gt;Send up to four relevant Dev.to tags.&lt;/li&gt;
&lt;li&gt;Create the Dev.to article with &lt;code&gt;canonical_url&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtbh4qni4m1o8ydioi5v.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtbh4qni4m1o8ydioi5v.webp" alt="Dark-mode GitHub Actions style automation workflow showing content parsing, eligibility filtering, image URL conversion, tag normalization, secret-protected API publishing, and successful publication" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Confirm Your Next.js Blog Has Stable Slugs
&lt;/h2&gt;

&lt;p&gt;Most Next.js portfolio blogs use one file per post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content/blog/my-article-slug.mdx
content/blog/another-article.mdx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filename becomes the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.yourdomain.com/blog/my-article-slug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you automate anything, make that contract reliable. Your script needs one predictable way to turn a file into a URL.&lt;/p&gt;

&lt;p&gt;For example:&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;const&lt;/span&gt; &lt;span class="nx"&gt;SITE_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://www.yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mdx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&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;canonicalUrl&lt;/span&gt; &lt;span class="o"&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;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use localhost URLs, preview deployment URLs, random query strings, or relative canonical paths. The canonical URL should be the final public article URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Self-Referential Canonical Tags on Your Site
&lt;/h2&gt;

&lt;p&gt;Every original article should include a canonical tag in the HTML head that points to itself:&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;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.yourdomain.com/blog/my-article-slug"&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;In the Next.js App Router, this usually belongs in &lt;code&gt;generateMetadata&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&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;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Metadata&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;slug&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="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBlogPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&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;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.yourdomain.com/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;images&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;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&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;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banner&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters: your own page should clearly say, "I am the canonical version." The Dev.to copy will later say the same thing from the other direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Make Your Blog Frontmatter Syndication-Ready
&lt;/h2&gt;

&lt;p&gt;Each post needs enough metadata to become a Dev.to article without manual cleanup.&lt;/p&gt;

&lt;p&gt;Use a frontmatter shape like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Improve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Portfolio&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Website"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-18"&lt;/span&gt;
&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;practical&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;guide&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;improving&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;portfolio&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;website&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;better&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;structure,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;speed,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;images,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;action."&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Next.js"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEO"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Portfolio"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Development"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Development"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;banner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/images/blog/example-cover.webp"&lt;/span&gt;
&lt;span class="na"&gt;updatedTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-18"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key fields for Dev.to are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes the Dev.to article title.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes the Dev.to description.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls the three-day delay.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;banner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes the Dev.to &lt;code&gt;main_image&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes Dev.to topic tags.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use a public image for &lt;code&gt;banner&lt;/code&gt;. Dev.to is not reading your site from the same origin, so a local image path like &lt;code&gt;/images/blog/cover.webp&lt;/code&gt; needs to be converted to &lt;code&gt;https://www.yourdomain.com/images/blog/cover.webp&lt;/code&gt; before publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Decide How Images Should Travel
&lt;/h2&gt;

&lt;p&gt;Images fail in syndication more often than text.&lt;/p&gt;

&lt;p&gt;There are two image types to handle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The article cover image.&lt;/li&gt;
&lt;li&gt;Images inside the article body.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the cover image, send Dev.to a public image URL:&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;main_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;absoluteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For body images, convert local paths to absolute URLs before publishing:&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;makeImageUrlsAbsolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&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="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/!&lt;/span&gt;&lt;span class="se"&gt;\\[&lt;/span&gt;&lt;span class="sr"&gt;([^&lt;/span&gt;&lt;span class="se"&gt;\\]&lt;/span&gt;&lt;span class="sr"&gt;]*&lt;/span&gt;&lt;span class="se"&gt;)\\&lt;/span&gt;&lt;span class="sr"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\\((\\&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;src&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&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="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;src&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your posts use custom MDX components like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;BlogImage
  src="/images/blog/example.webp"
  alt="Dashboard on a laptop"
  caption="Use real images that explain the article."
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convert them before sending to Dev.to:&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;convertBlogImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&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="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;BlogImage&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;s+&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?)\\&lt;/span&gt;&lt;span class="sr"&gt;s*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sr"&gt;/gs, &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;_, attrs&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;{
&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/src=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&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;alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/alt=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/caption=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&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="nx"&gt;src&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&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;alt&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;imageUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;caption&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;image&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n*&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;caption&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;image&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;Why this matters: the Dev.to API accepts Markdown. It does not understand your custom React components. Convert the content before it leaves your site.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw6vi89m9q3gfei0m7fm.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw6vi89m9q3gfei0m7fm.webp" alt="Canonical signal matrix comparing the original portfolio article and the DEV.to syndicated copy across publishing order, canonical signals, image handling, topic tags, and failure modes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Make Search Console Optional
&lt;/h2&gt;

&lt;p&gt;The normal workflow should not depend on a person submitting every URL by hand. Your automation should rely on stable article URLs, self-referential canonicals, a sitemap, internal links, RSS discovery, and the three-day Dev.to delay.&lt;/p&gt;

&lt;p&gt;For high-priority posts, request indexing for the original article as an optional acceleration step.&lt;/p&gt;

&lt;p&gt;Click-by-click:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://search.google.com/search-console" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select the property for your website.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;URL Inspection&lt;/strong&gt; search bar at the top.&lt;/li&gt;
&lt;li&gt;Paste the full original article URL.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Enter&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Wait for Google to inspect the URL.&lt;/li&gt;
&lt;li&gt;If the page is live, click &lt;strong&gt;Request Indexing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Wait for the confirmation message.&lt;/li&gt;
&lt;li&gt;Let the automated Dev.to workflow handle normal syndication after the three-day delay.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not required for Dev.to automation to work. It is useful when a post is especially important, timely, or tied to a campaign. For routine posts, let the automated workflow run without a manual Search Console task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Create a Dev.to API Key
&lt;/h2&gt;

&lt;p&gt;Do this from the Dev.to website:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Dev.to.&lt;/li&gt;
&lt;li&gt;Click your profile image in the top-right corner.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Extensions&lt;/strong&gt; in the settings sidebar.&lt;/li&gt;
&lt;li&gt;Scroll to &lt;strong&gt;DEV Community API Keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Type a short label, such as &lt;code&gt;GitHub blog syndication&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate API Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Copy the key once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not paste the key into your source code. Do not commit it to GitHub. Do not put it in a public blog post, screenshot, issue, pull request, or README.&lt;/p&gt;

&lt;p&gt;The key belongs in your automation environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Store the API Key as a GitHub Secret
&lt;/h2&gt;

&lt;p&gt;If your portfolio is on GitHub, store the key as a repository secret:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your GitHub repository.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the left sidebar, click &lt;strong&gt;Secrets and variables&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Actions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New repository secret&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Name the secret &lt;code&gt;DEVTO_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Paste the Dev.to API key into the value field.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add secret&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your workflow will read it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEVTO_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps the key out of your repo while still making it available to the scheduled job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Install the Packages Your Script Needs
&lt;/h2&gt;

&lt;p&gt;For an MDX or Markdown blog, a small Node script can usually do the job.&lt;/p&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;gray-matter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already have &lt;code&gt;gray-matter&lt;/code&gt;, you can skip that. Node 18 and newer includes &lt;code&gt;fetch&lt;/code&gt;, so you do not need a separate HTTP client in most modern Next.js projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Add a Safe Tag Mapper
&lt;/h2&gt;

&lt;p&gt;Dev.to tags should be short, lowercase, and relevant. Most posts should use no more than four.&lt;/p&gt;

&lt;p&gt;Your website tags may be human-readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Web Development&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SEO&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Content Marketing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dev.to tags should look more like:&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webdev&lt;/span&gt;&lt;span class="dl"&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;nextjs&lt;/span&gt;&lt;span class="dl"&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;seo&lt;/span&gt;&lt;span class="dl"&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;productivity&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;Add a mapper:&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;const&lt;/span&gt; &lt;span class="nx"&gt;TAG_MAP&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;Web Development&lt;/span&gt;&lt;span class="dl"&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;webdev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;SEO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seo&lt;/span&gt;&lt;span class="dl"&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;Content Marketing&lt;/span&gt;&lt;span class="dl"&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;contentmarketing&lt;/span&gt;&lt;span class="dl"&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;Web Design&lt;/span&gt;&lt;span class="dl"&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;webdesign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;JavaScript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;javascript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Nextjs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nextjs&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeDevToTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&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="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TAG_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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;Why this matters: tags are distribution channels on Dev.to. Bad tags make the article harder to discover, and too many tags can cause publishing problems. Keep the set focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10: Write the Sync Script
&lt;/h2&gt;

&lt;p&gt;Create a file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/publish-devto.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a complete generic version you can adapt:&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;fs&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;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&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;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;matter&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;gray-matter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SITE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SITE_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://www.yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEVTO_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEVTO_API_KEY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BLOG_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&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;blog&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;THREE_DAYS_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TAG_MAP&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;Web Development&lt;/span&gt;&lt;span class="dl"&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;webdev&lt;/span&gt;&lt;span class="dl"&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;Web Design&lt;/span&gt;&lt;span class="dl"&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;webdesign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;SEO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seo&lt;/span&gt;&lt;span class="dl"&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;Content Marketing&lt;/span&gt;&lt;span class="dl"&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;contentmarketing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;JavaScript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;javascript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Nextjs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nextjs&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing DEVTO_API_KEY.&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&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="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mdx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canonicalForSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&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;function&lt;/span&gt; &lt;span class="nf"&gt;absoluteUrl&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="k"&gt;if &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="nf"&gt;startsWith&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://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://&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="nx"&gt;url&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE_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="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&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="p"&gt;)&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="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="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeDevToTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TAG_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAtLeastThreeDaysOld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&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="nx"&gt;publishedTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&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;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publishedTime&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;publishedTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;THREE_DAYS_MS&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertBlogImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&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="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;BlogImage&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&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;*&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/g&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attrs&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="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/src=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&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;alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/alt=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/caption=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]([^&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&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;alt&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;absoluteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;caption&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;image&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n*&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;caption&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;image&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeMarkdownPortable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&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="nf"&gt;convertBlogImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/!&lt;/span&gt;&lt;span class="se"&gt;\[([^\]]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\]\((\/[^&lt;/span&gt;&lt;span class="sr"&gt;)&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;alt&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;absoluteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;Callout&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt; &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="nf"&gt;replace&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;*&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;Callout&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;devToRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&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="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="s2"&gt;`https://dev.to/api&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;endpoint&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&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;application/json&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;??&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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="nx"&gt;body&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;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Dev.to request failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;body&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="k"&gt;return&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getExistingArticles&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="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="k"&gt;for &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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;batch&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;devToRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/articles/me/all?page=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;per_page=100`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;batch&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="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&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;return&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;publishArticle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canonicalUrl&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="nf"&gt;devToRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/articles&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&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="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body_markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;main_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;absoluteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;normalizeDevToTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;published&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="p"&gt;},&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;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&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="nx"&gt;existingArticles&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;getExistingArticles&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;existingCanonicals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingArticles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&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;files&lt;/span&gt; &lt;span class="o"&gt;=&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BLOG_DIR&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mdx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&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="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&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="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BLOG_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;postSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;canonicalUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalForSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&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;source&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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="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;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAtLeastThreeDaysOld&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;date&lt;/span&gt;&lt;span class="p"&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;`Skipping &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: younger than three days.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;continue&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="nx"&gt;existingCanonicals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&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;`Skipping &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: already syndicated.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;continue&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;attribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`*Originally published on [your portfolio](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;).*`&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&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;attribution&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;makeMarkdownPortable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;article&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;publishArticle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;post&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;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&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;`Published &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&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;article&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&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;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;There are four important safety checks in that script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It refuses to run without &lt;code&gt;DEVTO_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It skips posts younger than three days.&lt;/li&gt;
&lt;li&gt;It checks existing Dev.to posts by &lt;code&gt;canonical_url&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It spaces out publish requests to avoid unnecessary API pressure.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 11: Add Package Scripts
&lt;/h2&gt;

&lt;p&gt;Add scripts to &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"publish:devto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node scripts/publish-devto.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"publish:devto:dry-run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node scripts/publish-devto.mjs --dry-run"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a safer first version, add a dry-run mode before allowing writes. A dry run should print what would be published without calling &lt;code&gt;POST /articles&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run publish:devto:dry-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script should print skipped and published posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skipping new-post: younger than three days.
Skipping old-post: already syndicated.
Published eligible-post: https://dev.to/username/eligible-post-slug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 12: Create the GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/workflows/devto-syndication.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a daily schedule plus a manual trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dev.to Syndication&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;13&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;syndicate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEVTO_API_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;SITE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.yourdomain.com&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Node&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish eligible posts to Dev.to&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run publish:devto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron time does not need to be exact. Pick a quiet time. The script controls eligibility, not the workflow schedule.&lt;/p&gt;

&lt;p&gt;Why daily works: a post published Monday at noon becomes eligible Thursday at noon. The next daily run after that will post it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 13: Test the Workflow Without Publishing
&lt;/h2&gt;

&lt;p&gt;Before publishing anything, test locally with a dry run or a temporary logging-only script.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;New posts are skipped.&lt;/li&gt;
&lt;li&gt;Old posts are eligible.&lt;/li&gt;
&lt;li&gt;Existing Dev.to posts are skipped.&lt;/li&gt;
&lt;li&gt;Canonical URLs are absolute and correct.&lt;/li&gt;
&lt;li&gt;Cover images are absolute.&lt;/li&gt;
&lt;li&gt;Body images are absolute.&lt;/li&gt;
&lt;li&gt;Tags are lowercase and limited to four.&lt;/li&gt;
&lt;li&gt;No API key is printed in the logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then run the GitHub Action manually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your GitHub repository.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Actions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Dev.to Syndication&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Run workflow&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose the branch.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Run workflow&lt;/strong&gt; again.&lt;/li&gt;
&lt;li&gt;Open the workflow run.&lt;/li&gt;
&lt;li&gt;Watch the log output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The logs should show skipped or published posts, but never the API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 14: Verify the Dev.to Article
&lt;/h2&gt;

&lt;p&gt;After the first publish, open the Dev.to article and check it manually.&lt;/p&gt;

&lt;p&gt;Confirm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The title is correct.&lt;/li&gt;
&lt;li&gt;The cover image appears.&lt;/li&gt;
&lt;li&gt;Body images appear.&lt;/li&gt;
&lt;li&gt;Code blocks are readable.&lt;/li&gt;
&lt;li&gt;Tables still make sense.&lt;/li&gt;
&lt;li&gt;The top attribution link points to the original post.&lt;/li&gt;
&lt;li&gt;The Dev.to canonical URL points to the original post.&lt;/li&gt;
&lt;li&gt;Tags are relevant and not spammy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you use the Dev.to editor, you can also check the canonical manually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Dev.to post.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the post settings.&lt;/li&gt;
&lt;li&gt;Find &lt;strong&gt;Canonical URL&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Confirm it matches your original article URL exactly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the basic Markdown editor, the same value may appear as frontmatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.yourdomain.com/blog/my-article-slug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 15: Keep the Original URL Stable
&lt;/h2&gt;

&lt;p&gt;Canonical syndication depends on stable URLs.&lt;/p&gt;

&lt;p&gt;Avoid changing slugs after syndication. If you must change one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a permanent redirect from the old URL to the new URL.&lt;/li&gt;
&lt;li&gt;Update the Dev.to article's &lt;code&gt;canonical_url&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update the attribution link.&lt;/li&gt;
&lt;li&gt;Re-submit the new original URL in Google Search Console.&lt;/li&gt;
&lt;li&gt;Confirm the old URL does not return a broken page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Broken canonical URLs waste the value of the whole setup. A canonical tag that points to a 404 is not helping the original article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 16: Build a Post-Publish Checklist
&lt;/h2&gt;

&lt;p&gt;Automation should reduce manual work, not remove responsibility.&lt;/p&gt;

&lt;p&gt;Use this automation-first checklist for every new article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Article is live on your own domain.&lt;/li&gt;
&lt;li&gt;[ ] Original page returns &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Original page has a self-referential canonical URL.&lt;/li&gt;
&lt;li&gt;[ ] Original page has a usable title and meta description.&lt;/li&gt;
&lt;li&gt;[ ] Cover image is public and absolute.&lt;/li&gt;
&lt;li&gt;[ ] Body images are public or convertible to absolute URLs.&lt;/li&gt;
&lt;li&gt;[ ] Three days have passed.&lt;/li&gt;
&lt;li&gt;[ ] Dev.to copy includes &lt;code&gt;canonical_url&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Dev.to copy includes the article image.&lt;/li&gt;
&lt;li&gt;[ ] Dev.to copy uses no more than four relevant tags.&lt;/li&gt;
&lt;li&gt;[ ] Dev.to copy includes attribution to the original post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional for priority posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Google Search Console indexing request is submitted for the original URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Helps With Canonical Link Equity
&lt;/h2&gt;

&lt;p&gt;"Link juice" is casual SEO language for link equity, authority, or ranking signals that flow through links and canonical relationships. The phrase is imperfect, but the practical goal is clear: you want the original article on your domain to receive the strongest possible credit.&lt;/p&gt;

&lt;p&gt;The setup supports that goal in four ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your site publishes first.&lt;/li&gt;
&lt;li&gt;Your original page declares itself canonical.&lt;/li&gt;
&lt;li&gt;Dev.to declares your original page as canonical.&lt;/li&gt;
&lt;li&gt;The Dev.to body includes a visible attribution link back to the original.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google still makes its own canonicalization decisions, and canonical tags are signals, not commands. But consistent signals matter. If your own page, your sitemap, your RSS feed, your Dev.to copy, and your internal links all point to the same original URL, you are making the decision easy for crawlers.&lt;/p&gt;

&lt;p&gt;That is the real purpose of the three-day delay. It gives the original article a head start before the high-authority duplicate appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Do not publish to Dev.to immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Give your site time to be crawled first. Three days is a practical default.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not forget the canonical URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without &lt;code&gt;canonical_url&lt;/code&gt;, Dev.to may become the stronger version in search simply because the platform has more authority than your portfolio.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not use relative image paths.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dev.to cannot reliably render &lt;code&gt;/images/blog/example.webp&lt;/code&gt; unless it knows your domain. Convert it to &lt;code&gt;https://www.yourdomain.com/images/blog/example.webp&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not send every website tag to Dev.to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick four relevant tags. Treat tags like reader targeting, not keyword stuffing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not expose your API key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use GitHub Secrets, environment variables, or your deployment platform's secret manager.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not rewrite internal links to Dev.to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the goal is canonical value for your site, keep links pointing back to your original domain where appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual Fallback: RSS Import
&lt;/h2&gt;

&lt;p&gt;If you do not want to use the API yet, Dev.to also supports RSS imports.&lt;/p&gt;

&lt;p&gt;Click-by-click:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Dev.to.&lt;/li&gt;
&lt;li&gt;Click your profile image.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Extensions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Find &lt;strong&gt;Publishing to DEV Community from RSS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste your RSS feed URL.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Mark the RSS source as canonical URL by default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Save the feed settings.&lt;/li&gt;
&lt;li&gt;Review imported drafts before publishing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RSS import is simpler. The API workflow gives you more control over the three-day delay, image conversion, existing-post checks, and tag mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Recommended Workflow
&lt;/h2&gt;

&lt;p&gt;For a Next.js portfolio, the cleanest process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the blog post in your repo.&lt;/li&gt;
&lt;li&gt;Use an absolute cover image URL.&lt;/li&gt;
&lt;li&gt;Deploy the original post to your domain.&lt;/li&gt;
&lt;li&gt;Let the post sit for three days while your sitemap, feed, and internal links expose the original.&lt;/li&gt;
&lt;li&gt;Optionally request indexing in Google Search Console for priority posts.&lt;/li&gt;
&lt;li&gt;Run a scheduled GitHub Action every day.&lt;/li&gt;
&lt;li&gt;Have the script publish only eligible posts to Dev.to.&lt;/li&gt;
&lt;li&gt;Send &lt;code&gt;canonical_url&lt;/code&gt; with every Dev.to article.&lt;/li&gt;
&lt;li&gt;Include the main image and convert local body images.&lt;/li&gt;
&lt;li&gt;Limit Dev.to tags to four.&lt;/li&gt;
&lt;li&gt;Periodically audit Dev.to to make sure every syndicated article points back to the original.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the balance: own the original, syndicate for reach, and make every duplicate point back to your domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DEV Community Help, &lt;a href="https://dev.to/help/organizations/importing-your-organizations-content"&gt;"Importing Your Organization's Content"&lt;/a&gt;, accessed August 18, 2026.&lt;/li&gt;
&lt;li&gt;Forem API Docs, &lt;a href="https://developers.forem.com/api/v0#tag/articles/operation/createArticle" rel="noopener noreferrer"&gt;"Create a new article"&lt;/a&gt;, accessed August 18, 2026.&lt;/li&gt;
&lt;li&gt;Google Search Central, &lt;a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls" rel="noopener noreferrer"&gt;"How to specify a canonical with rel=canonical and other methods"&lt;/a&gt;, accessed August 18, 2026.&lt;/li&gt;
&lt;li&gt;DEV Team, &lt;a href="https://dev.to/devteam/revamped-rss-feed-imports-3j1e"&gt;"Revamped RSS Feed Imports"&lt;/a&gt;, March 9, 2026.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>devto</category>
      <category>seo</category>
      <category>contentstrategy</category>
    </item>
    <item>
      <title>Social Media Content Calendar for Small Business Leads</title>
      <dc:creator>Ryan VerWey</dc:creator>
      <pubDate>Tue, 18 Aug 2026 15:30:52 +0000</pubDate>
      <link>https://dev.to/rverwey/social-media-content-calendar-for-small-business-leads-1g2d</link>
      <guid>https://dev.to/rverwey/social-media-content-calendar-for-small-business-leads-1g2d</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.echoeffect.net/blog/social-media-content-calendar-small-business-leads-2026" rel="noopener noreferrer"&gt;echoeffect.net&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A social media content calendar should do more than remind you to post. For a small business, the useful version connects each post to an audience, a clear offer, a website destination, a tracking link, and a follow-up decision. That is how social media starts supporting leads instead of only producing likes.&lt;/p&gt;

&lt;p&gt;The short answer: plan one month at a time, publish consistently, send interested people to the right website page, tag links with campaign parameters, and review both platform insights and website traffic before changing the next month. Meta Business Suite can schedule Facebook and Instagram content and show paid and organic activity, while Google Analytics can identify campaigns that referred traffic to your site (Meta Business Help Center, n.d.-a; Google Analytics Help, n.d.-a).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; A strong content calendar is a conversion system. Every planned post should have a business role: awareness, trust-building, traffic, lead capture, retention, or direct offer support.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Most Content Calendars Fail
&lt;/h2&gt;

&lt;p&gt;Most small business calendars fail because they start with empty dates instead of business goals. The owner asks, "What should we post on Tuesday?" instead of "What decision do we want a real customer to make this month?"&lt;/p&gt;

&lt;p&gt;That produces a familiar pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Random holidays that do not connect to the offer.&lt;/li&gt;
&lt;li&gt;Platform trends copied without a clear audience fit.&lt;/li&gt;
&lt;li&gt;Posts that get engagement but send nobody to the website.&lt;/li&gt;
&lt;li&gt;Links with no UTM tracking, so traffic becomes hard to attribute.&lt;/li&gt;
&lt;li&gt;No review cycle, so the same weak content repeats next month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A calendar is useful only when it reduces guesswork. For Echo Effect clients, that means connecting platform execution with the website, landing pages, service pages, analytics, and reporting. If your posts create interest but the website does not explain the next step, the calendar is doing only half the job.&lt;/p&gt;

&lt;p&gt;For deeper social execution, see Echo Effect's &lt;a href="https://dev.to/products/social-media"&gt;social media consulting services&lt;/a&gt;. If your website is the weak link after the click, review the &lt;a href="https://dev.to/products/web-design"&gt;web design and development services&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Should a Small Business Content Calendar Include?
&lt;/h2&gt;

&lt;p&gt;A practical social media content calendar should include the fields that affect publishing, measurement, and conversion. Keep it simple enough to use every week.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Calendar Field&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;td&gt;Facebook, Instagram, LinkedIn, and X behave differently, so do not paste the same plan everywhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audience&lt;/td&gt;
&lt;td&gt;Name who the post is for: new prospects, warm leads, past customers, partners, or referral sources.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content role&lt;/td&gt;
&lt;td&gt;Label the post as awareness, education, proof, offer, event, retention, or community.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format&lt;/td&gt;
&lt;td&gt;Choose the right asset: short video, carousel, static image, text post, story, article link, or poll.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website destination&lt;/td&gt;
&lt;td&gt;Map the post to a service page, contact page, blog article, booking page, or landing page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracking link&lt;/td&gt;
&lt;td&gt;Use consistent UTM parameters so campaigns can be found in analytics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner and status&lt;/td&gt;
&lt;td&gt;Track draft, review, approved, scheduled, published, and measured.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;Record useful outcomes: clicks, leads, comments, saves, replies, form starts, or booked calls.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Meta Business Suite supports creating and scheduling Facebook and Instagram posts from desktop, and its Insights area helps businesses understand paid and organic activity across a Facebook Page and Instagram account (Meta Business Help Center, n.d.-a; Meta Business Help Center, n.d.-b). That gives owners a starting point, but platform data should not be the only source of truth.&lt;/p&gt;

&lt;p&gt;Your website analytics matter because they show what happened after the click. Google Analytics describes the Traffic acquisition report as a way to understand where website and app visitors came from (Google Analytics Help, n.d.-b). If social media is meant to drive leads, then website sessions, engaged visits, form actions, and consultation requests deserve attention alongside reach and engagement.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0covca54iewe4yhnvwgf.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0covca54iewe4yhnvwgf.webp" alt="Notebook page labeled marketing strategy beside a keyboard on a desk" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A content calendar works best when each post has a business role before it has a caption.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Build a Calendar That Drives Leads?
&lt;/h2&gt;

&lt;p&gt;Start with the offer, not the platform. If the business goal is consultation requests, the calendar should support the path from curiosity to contact.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Pick One Monthly Business Goal
&lt;/h3&gt;

&lt;p&gt;Choose one primary goal for the month:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;More consultation requests.&lt;/li&gt;
&lt;li&gt;More calls from a service page.&lt;/li&gt;
&lt;li&gt;More traffic to a new blog article.&lt;/li&gt;
&lt;li&gt;More quote requests for one service.&lt;/li&gt;
&lt;li&gt;More retargeting audience growth before a paid campaign.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One goal does not mean every post is promotional. It means every post has a job in the same customer journey. A contractor might spend the month educating homeowners about when a repair becomes urgent. A consultant might spend the month explaining one problem, showing proof of expertise, and sending prospects to a discovery call.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Map Content Roles Across the Month
&lt;/h3&gt;

&lt;p&gt;Use a balanced mix. A simple weekly rhythm might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Content Role&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;Education&lt;/td&gt;
&lt;td&gt;Explain a common customer problem in plain language.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;Proof&lt;/td&gt;
&lt;td&gt;Show a process, before-and-after context, or client-safe result without inventing claims.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;Offer&lt;/td&gt;
&lt;td&gt;Link to the relevant service page or consultation page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekend&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Ask a useful question, recap an event, or share a practical tip.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LinkedIn's own Pages best practices frame a company page as a way to establish thought leadership, build brand awareness, and move an audience to action (LinkedIn Marketing Solutions, n.d.). That same principle applies across platforms: content should not only fill space, it should help the audience understand why the business is credible and what to do next.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Connect Posts to the Right Website Page
&lt;/h3&gt;

&lt;p&gt;Do not send every post to the homepage. Match the link to the intent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A beginner education post can link to a blog article.&lt;/li&gt;
&lt;li&gt;A comparison post can link to a service page.&lt;/li&gt;
&lt;li&gt;A high-intent offer post can link to the contact page.&lt;/li&gt;
&lt;li&gt;A paid social campaign can link to a focused landing page.&lt;/li&gt;
&lt;li&gt;A trust-building post can link to an about page, review section, or case-study-style article if one exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where social media and web development overlap. The calendar creates attention, but the website captures demand. If the website is slow, vague, hard to use on mobile, or missing a clear call to action, stronger posting will not fix the lead path. Pair this article with the &lt;a href="https://dev.to/blog/local-business-website-essentials"&gt;local business website essentials guide&lt;/a&gt; and the &lt;a href="https://dev.to/blog/core-web-vitals-small-business-checklist-2026"&gt;Core Web Vitals checklist&lt;/a&gt; when auditing the destination pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use UTM Links Before You Publish
&lt;/h3&gt;

&lt;p&gt;UTM parameters are small pieces of text added to a URL so analytics tools can identify campaign traffic. Google's URL builder documentation explains that campaign parameters help identify campaigns that refer traffic (Google Analytics Help, n.d.-a).&lt;/p&gt;

&lt;p&gt;For a small business, keep naming consistent:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;UTM Field&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;facebook&lt;/code&gt;, &lt;code&gt;instagram&lt;/code&gt;, &lt;code&gt;linkedin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;organic_social&lt;/code&gt;, &lt;code&gt;paid_social&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_campaign&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;august_2026_consultation_push&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;service_page_carousel&lt;/code&gt;, &lt;code&gt;owner_tip_video&lt;/code&gt;, &lt;code&gt;faq_post&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use lowercase names, avoid spaces, and document the pattern inside the calendar. The goal is not analytics perfection. The goal is to stop guessing whether social posts are creating website demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Review the Calendar Like a Sales Tool
&lt;/h3&gt;

&lt;p&gt;At the end of the month, do not ask only which post got the most likes. Ask better questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which posts drove qualified website traffic?&lt;/li&gt;
&lt;li&gt;Which links produced form starts, calls, bookings, or email inquiries?&lt;/li&gt;
&lt;li&gt;Which posts created real conversations with prospects?&lt;/li&gt;
&lt;li&gt;Which platform reached the best-fit audience?&lt;/li&gt;
&lt;li&gt;Which content role was missing?&lt;/li&gt;
&lt;li&gt;Which website page failed to move visitors forward?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Meta insights can help identify content performance on Facebook and Instagram, while Google Analytics can show acquisition patterns after people reach the website (Meta Business Help Center, n.d.-b; Google Analytics Help, n.d.-b). Neither system tells the whole story alone. Use both.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgik9694j7ejoz4bn4kh1.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgik9694j7ejoz4bn4kh1.webp" alt="To-do list beside a laptop on a dark workspace" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The best calendar is simple enough to use weekly and specific enough to guide better decisions next month.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A 30-Day Content Calendar Framework
&lt;/h2&gt;

&lt;p&gt;Use this as a starter structure for one service line.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Week&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;th&gt;Content Ideas&lt;/th&gt;
&lt;th&gt;Website Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Problem awareness&lt;/td&gt;
&lt;td&gt;Common mistakes, warning signs, myths, quick tips&lt;/td&gt;
&lt;td&gt;Educational blog article&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Trust and expertise&lt;/td&gt;
&lt;td&gt;Process explanation, FAQ, service comparison, behind-the-scenes workflow&lt;/td&gt;
&lt;td&gt;Service page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Proof and specificity&lt;/td&gt;
&lt;td&gt;Review-safe quote, project detail without private data, checklist, decision guide&lt;/td&gt;
&lt;td&gt;Related article or about page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Offer and conversion&lt;/td&gt;
&lt;td&gt;Consultation invitation, limited-scope audit, downloadable checklist, direct next step&lt;/td&gt;
&lt;td&gt;Contact page or landing page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This framework keeps the month balanced. It avoids a feed that is all selling and avoids a feed that is all education with no business outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Metrics Should You Track?
&lt;/h2&gt;

&lt;p&gt;Track metrics in layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Useful Metrics&lt;/th&gt;
&lt;th&gt;What It Tells You&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Platform reach&lt;/td&gt;
&lt;td&gt;Reach, impressions, profile visits&lt;/td&gt;
&lt;td&gt;Did the post get seen by enough people?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform engagement&lt;/td&gt;
&lt;td&gt;Comments, saves, shares, replies, link clicks&lt;/td&gt;
&lt;td&gt;Did the topic create interest or action?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website behavior&lt;/td&gt;
&lt;td&gt;Sessions, engaged sessions, landing page views&lt;/td&gt;
&lt;td&gt;Did the click become a real visit?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lead behavior&lt;/td&gt;
&lt;td&gt;Form submissions, calls, bookings, quote requests&lt;/td&gt;
&lt;td&gt;Did the visit create a business opportunity?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales context&lt;/td&gt;
&lt;td&gt;Lead quality, close rate, common objections&lt;/td&gt;
&lt;td&gt;Did the content attract the right audience?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Vanity metrics are not useless, but they are incomplete. A post with modest reach and two qualified consultation requests can be more valuable than a viral post from people who will never buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ: Social Media Content Calendars for Small Businesses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How far ahead should a small business plan social media content?
&lt;/h3&gt;

&lt;p&gt;Plan one month at a time, then review weekly. Monthly planning gives enough structure for campaigns and approvals, while weekly review keeps the calendar flexible when offers, events, or customer questions change.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should a small business post?
&lt;/h3&gt;

&lt;p&gt;Posting frequency depends on the platform, audience, and internal capacity. A sustainable schedule that gets reviewed and improved is better than an aggressive schedule that collapses after two weeks. Start with three to five useful posts per week across your priority platforms, then adjust based on real engagement and lead quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every post link to the website?
&lt;/h3&gt;

&lt;p&gt;No. Some posts should create trust, start conversations, or answer questions directly on the platform. But every calendar should include regular posts that send interested people to a relevant website page, especially when the goal is leads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need paid ads with a content calendar?
&lt;/h3&gt;

&lt;p&gt;Not always. Organic planning can improve consistency and clarify which messages resonate. Paid ads make more sense when the offer, website destination, audience, tracking, and creative have already been tested enough to spend with discipline.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the biggest content calendar mistake?
&lt;/h3&gt;

&lt;p&gt;The biggest mistake is planning posts without a measurement loop. If you do not review what created traffic, leads, conversations, and customer questions, the calendar becomes a publishing habit instead of a marketing system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Echo Effect manage this for my business?
&lt;/h3&gt;

&lt;p&gt;Yes. Echo Effect's social media services include strategy, content creation, scheduling, platform optimization, reporting, and paid campaign support. The right fit depends on your goals, platforms, approval process, and whether your website is ready to convert visitors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;A social media content calendar should make your marketing easier to run and easier to measure. The useful version connects posts to business goals, audience intent, website destinations, tracking links, and monthly decisions.&lt;/p&gt;

&lt;p&gt;If your business is posting inconsistently, relying on vanity metrics, or sending social traffic to weak website pages, Echo Effect can help build a cleaner system. Start with the &lt;a href="https://dev.to/products/social-media"&gt;social media consulting service&lt;/a&gt;, review your website's role in the funnel with &lt;a href="https://dev.to/products/web-design"&gt;web design and development&lt;/a&gt;, or &lt;a href="https://dev.to/contact"&gt;schedule a consultation&lt;/a&gt; to map the next 30 days around leads instead of guesswork.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Google Analytics Help. (n.d.-a). &lt;a href="https://support.google.com/analytics/answer/10917952?hl=en" rel="noopener noreferrer"&gt;URL builders: Collect campaign data with custom URLs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Google Analytics Help. (n.d.-b). &lt;a href="https://support.google.com/analytics/answer/12923437?hl=en" rel="noopener noreferrer"&gt;[GA4] Traffic acquisition report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;LinkedIn Marketing Solutions. (n.d.). &lt;a href="https://business.linkedin.com/advertise/linkedin-pages/best-practices" rel="noopener noreferrer"&gt;LinkedIn Pages best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meta Business Help Center. (n.d.-a). &lt;a href="https://www.facebook.com/business/help/942827662903020" rel="noopener noreferrer"&gt;Create and manage posts in Meta Business Suite on desktop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meta Business Help Center. (n.d.-b). &lt;a href="https://www.facebook.com/business/help/700570830721044" rel="noopener noreferrer"&gt;About Insights in Meta Business Suite on desktop&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>socialmedia</category>
      <category>marketing</category>
      <category>contentmarketing</category>
      <category>business</category>
    </item>
  </channel>
</rss>
