<?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: jake</title>
    <description>The latest articles on DEV Community by jake (@jakexkim).</description>
    <link>https://dev.to/jakexkim</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%2F3838932%2F7985cd45-39fb-42e3-bfc4-8acbf05226d2.png</url>
      <title>DEV Community: jake</title>
      <link>https://dev.to/jakexkim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jakexkim"/>
    <language>en</language>
    <item>
      <title>How to Review a Signup Regression After a Next.js Release</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:31:32 +0000</pubDate>
      <link>https://dev.to/jakexkim/how-to-review-a-signup-regression-after-a-nextjs-release-53g1</link>
      <guid>https://dev.to/jakexkim/how-to-review-a-signup-regression-after-a-nextjs-release-53g1</guid>
      <description>&lt;p&gt;A signup regression after a Next.js release should not start with panic. It should start with a narrow review.&lt;/p&gt;

&lt;p&gt;The dangerous version is familiar: &lt;a href="https://dev.to/blog/your-next-js-deploy-was-green-did-signup-get-worse"&gt;a deploy ships green and signup quietly gets worse&lt;/a&gt;, and a few hours later signup looks weaker. Someone opens product analytics. Someone checks Sentry. Someone asks which pull request touched auth. Someone else wonders whether traffic changed. The team has evidence, but not a review.&lt;/p&gt;

&lt;p&gt;The useful question is smaller: did this deploy make the signup path worse, and what evidence supports the next action?&lt;/p&gt;

&lt;h2&gt;
  
  
  Which signup path should you name before opening dashboards?
&lt;/h2&gt;

&lt;p&gt;"Signup is down" is not specific enough.&lt;/p&gt;

&lt;p&gt;Signup may include a marketing page, account creation form, email verification, company profile, workspace creation, plan selection, invite acceptance, and first activation step. A Next.js release might affect only one of those pieces. If the review treats signup as one blob, the team can miss the actual regression.&lt;/p&gt;

&lt;p&gt;Start by naming the path precisely: &lt;code&gt;/signup&lt;/code&gt;, &lt;code&gt;/signup/company&lt;/code&gt;, &lt;code&gt;/auth/callback&lt;/code&gt;, &lt;code&gt;/join/[invite]&lt;/code&gt;, &lt;code&gt;/onboarding/workspace&lt;/code&gt;, or whatever route actually changed. Then name the success event. It should be a user outcome, not a page view. Account created, email verified, workspace created, invite accepted, or onboarding completed is stronger than "visited signup."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://posthog.com/docs/product-analytics/funnels" rel="noopener noreferrer"&gt;PostHog's funnel docs&lt;/a&gt; are useful here because they separate steps, drop-off, conversion rate, breakdowns, and historical trends. A funnel can show where people left. It still needs deploy context before it becomes a release review.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you freeze the release context?
&lt;/h2&gt;

&lt;p&gt;Capture the deploy while it is still fresh:&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;What to record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Commit SHA, deploy URL, deploy time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull request&lt;/td&gt;
&lt;td&gt;Auth, form, validation, layout, tracking, copy, pricing, invite logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;The team or person responsible for the signup path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;The precise route or flow being reviewed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metric&lt;/td&gt;
&lt;td&gt;Signup completion, invite acceptance, verification success, workspace creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Before and after intervals with comparable traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Product funnel, Sentry issue, route trace, logs, page analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;Stable, watch, investigate, rollback candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/deployments" rel="noopener noreferrer"&gt;Vercel's deployment docs&lt;/a&gt; matter because deployments carry concrete release context: generated URLs, environments, commits, logs, and project metadata. Use that context as the anchor. Do not let the review become "sometime after the release."&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you compare signup against itself?
&lt;/h2&gt;

&lt;p&gt;Do not start with all-site conversion. Signup can regress while the rest of the product looks normal. It can also look worse because a campaign sent colder traffic into the top of the funnel.&lt;/p&gt;

&lt;p&gt;Use the deploy timestamp as the anchor and compare a similar window before and after release. The window should match traffic shape where possible. A Monday morning release should not be compared casually against late Friday night traffic unless volume and source mix are stable.&lt;/p&gt;

&lt;p&gt;The comparison should answer the practical question without pretending one chart proves causality. Did the protected signup metric move after deploy? Did the PR touch code, content, routing, auth, pricing, flags, or validation on that path? Did traffic source, geography, device mix, or campaign exposure change enough to explain it? Did companion evidence point at the same route?&lt;/p&gt;

&lt;p&gt;The comparison should preserve uncertainty. A dip after deploy is not automatically caused by the deploy. A clean error dashboard is not automatically proof that signup is healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runtime evidence should you check second?
&lt;/h2&gt;

&lt;p&gt;After the path and metric are named, check runtime evidence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/" rel="noopener noreferrer"&gt;Sentry's Next.js docs&lt;/a&gt; cover error monitoring, tracing, session replay, logs, and profiling for Next.js. In a signup review, Sentry is where you look for new exceptions on the signup route, failed server actions, hydration errors on the form, slow transactions around validation or workspace creation, and replay samples where users hit an error state.&lt;/p&gt;

&lt;p&gt;The runtime signal is strongest when it lines up with the product signal, because &lt;a href="https://dev.to/blog/posthog-shows-behavior-sentry-shows-breakage-deploy-review-needs-both"&gt;PostHog shows behavior while Sentry shows breakage and a deploy review needs both&lt;/a&gt;. "Signup completion dropped and validation exceptions appeared on &lt;code&gt;/signup/company&lt;/code&gt; after the validation PR" is a review. "Sentry has a new issue somewhere" is a lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check route and span evidence when the symptom is soft
&lt;/h2&gt;

&lt;p&gt;Signup regressions are not always loud. A route can become slower without throwing. A server action can retry. A third-party auth call can add latency. A dynamic route can fetch more than it did yesterday.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; shows default spans for route rendering, fetches, API routes, and metadata work, plus custom spans. That instrumentation helps explain soft regressions: a route render got slower, an auth callback made a slower external call, workspace creation moved from one backend call to several, or a fetch cache change added latency in the first signup step.&lt;/p&gt;

&lt;p&gt;The review does not need every span. It needs enough route evidence to explain whether the deploy plausibly changed the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check behavior and traffic context
&lt;/h2&gt;

&lt;p&gt;Product analytics can show the behavioral shape of the regression: more users start signup but fewer complete it, one step loses more people than usual, mobile moves while desktop stays stable, or a flagged cohort behaves differently. Vercel Web Analytics can add page-level context through visitor and page behavior. PostHog can add events, cohorts, flags, and funnel breakdowns. Use those signals to prevent false blame.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signup completion dropped after deploy, but traffic from a new campaign doubled and those visitors historically convert lower. Sentry is quiet. Mark watch and recheck with segmented traffic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is very different from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signup completion dropped after deploy, traffic mix is stable, and Sentry shows new validation failures on the same route. Mark investigate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Use four decision states
&lt;/h2&gt;

&lt;p&gt;The review should end with a state. Stable means signup stayed in range. Watch means signup moved but the evidence is mixed. Investigate means signup moved and the deploy plausibly touched it. Rollback candidate means signup moved and technical evidence points to the release. This language keeps the team from treating every dip as an incident or every clean error dashboard as a dismissal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why write the note while the evidence is fresh?
&lt;/h2&gt;

&lt;p&gt;A good signup regression note is short:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy &lt;code&gt;9f3c&lt;/code&gt; changed company signup validation. &lt;code&gt;/signup/company&lt;/code&gt; completion dropped in the first stable post-deploy window. Traffic mix was normal. Sentry shows new validation errors on the same route. Marked investigate; owner is onboarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A weak note is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signup looks down after the deploy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The weak note forces the next person to reconstruct the release. The strong note lets them act.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/product"&gt;TrueClara&lt;/a&gt; is built for that strong note. It keeps deploy metadata, path ownership, signup metrics, evidence links, and the decision together. Your analytics and monitoring tools remain the evidence sources. TrueClara keeps the signup review from dissolving into tabs.&lt;/p&gt;

&lt;p&gt;The next time signup moves after a Next.js release, do not start by asking which dashboard is right. Start with the path. Anchor it to the deploy. Layer the evidence. Record the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://posthog.com/docs/product-analytics/funnels" rel="noopener noreferrer"&gt;PostHog funnels&lt;/a&gt; - Product analytics documentation for funnel steps, conversion, breakdowns, and trends.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/" rel="noopener noreferrer"&gt;Sentry Next.js docs&lt;/a&gt; - Sentry documentation for Next.js errors, traces, replay, logs, and profiling.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; - Official Next.js instrumentation guide for route, fetch, API, and custom spans.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://vercel.com/docs/deployments" rel="noopener noreferrer"&gt;Vercel deployments&lt;/a&gt; - Vercel documentation for deploy methods, URLs, environments, and release metadata.&lt;/li&gt;
&lt;li&gt;[C5] &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Web Analytics&lt;/a&gt; - Vercel documentation for page-level visitor behavior.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>observability</category>
      <category>devops</category>
      <category>sentry</category>
    </item>
    <item>
      <title>PostHog Behavior, Sentry Breakage: Deploy Review Needs Both</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Thu, 09 Jul 2026 14:30:42 +0000</pubDate>
      <link>https://dev.to/jakexkim/posthog-behavior-sentry-breakage-deploy-review-needs-both-2f2p</link>
      <guid>https://dev.to/jakexkim/posthog-behavior-sentry-breakage-deploy-review-needs-both-2f2p</guid>
      <description>&lt;p&gt;PostHog and Sentry are not interchangeable tools. They answer different questions.&lt;/p&gt;

&lt;p&gt;PostHog is where a team looks when it wants to understand product behavior: events, funnels, cohorts, feature flags, experiments, session replay, and conversion movement. Sentry is where a team looks when it wants to understand software failure: exceptions, traces, performance issues, releases, replays, and the technical context around a broken experience.&lt;/p&gt;

&lt;p&gt;After a deploy, both are useful. Neither one, alone, is the full release review.&lt;/p&gt;

&lt;p&gt;The question is not "PostHog or Sentry?" The better question is: did this deploy hurt the route we care about, and which evidence proves the next action?&lt;/p&gt;

&lt;h2&gt;
  
  
  What does each tool answer after a deploy?
&lt;/h2&gt;

&lt;p&gt;A Next.js release can damage a product path without creating a loud exception. It can also create errors without changing the customer outcome enough to matter. That is why deploy review gets messy when a team treats one dashboard as the source of truth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry for Next.js&lt;/a&gt; is strong when the release causes runtime trouble. It can surface errors, performance spans, traces, replays, and release-adjacent debugging context. If a server action starts throwing, a hydration issue appears, or a route slows down, Sentry is often where the technical explanation begins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog's Next.js docs&lt;/a&gt; are strong on product instrumentation. Events, feature flags, user behavior, and funnels help answer whether people completed the path after the release. If signup completion dipped or onboarding step two started losing users, product analytics is the right evidence source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Analytics&lt;/a&gt; can explain page-level behavior and traffic changes. The &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; shows the instrumentation layer underneath routes, fetches, and custom spans.&lt;/p&gt;

&lt;p&gt;Each tool is useful. The deploy review is the layer that asks them all the same question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Best evidence source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Did users complete the path at the same rate?&lt;/td&gt;
&lt;td&gt;PostHog funnel or event analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did the release introduce new runtime failures?&lt;/td&gt;
&lt;td&gt;Sentry issues, traces, and replays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did traffic mix or page behavior change?&lt;/td&gt;
&lt;td&gt;Vercel Analytics and acquisition context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did a route render, fetch, or backend call change?&lt;/td&gt;
&lt;td&gt;OpenTelemetry spans and traces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did the PR actually touch the path?&lt;/td&gt;
&lt;td&gt;Pull request diff and deploy metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why does deploy impact fall between dashboards?
&lt;/h2&gt;

&lt;p&gt;Most teams already have enough telemetry to see the symptoms. The hard part is keeping the symptoms attached to the release.&lt;/p&gt;

&lt;p&gt;A deploy goes out at 10:14. Signup dips by 11:00. Sentry shows a small increase in validation errors. PostHog shows a larger drop at one step. Vercel shows traffic was normal. The PR changed a validation rule. That is a useful story, and it mirrors the pattern in &lt;a href="https://dev.to/blog/your-next-js-deploy-was-green-did-signup-get-worse"&gt;a green Next.js deploy that quietly hurt signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But if those facts live in separate tabs, the team has to assemble the story every time. One person pastes a Sentry link. Another posts a funnel screenshot. Someone asks which commit shipped. Someone else asks whether traffic changed. By the time the group agrees on the route, half the context is already stale.&lt;/p&gt;

&lt;p&gt;Deploy impact is not a dashboard category. It is an operating question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which release created the question?&lt;/li&gt;
&lt;li&gt;Which path could a customer feel?&lt;/li&gt;
&lt;li&gt;Which metric says the path got better or worse?&lt;/li&gt;
&lt;li&gt;Which evidence supports the movement?&lt;/li&gt;
&lt;li&gt;What decision did the team make?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TrueClara keeps that question as a case file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not force product analytics to explain engineering failure
&lt;/h2&gt;

&lt;p&gt;PostHog can show that signup completion dropped. It can show the step where users left. It can show whether a feature flag was involved. It can show cohorts, replays, and events around the behavior.&lt;/p&gt;

&lt;p&gt;But a product analytics tool is not the best place to inspect stack traces, slow spans, server-side exceptions, or release health. If the route moved because a server action started failing under a certain payload, Sentry or trace data should carry that investigation.&lt;/p&gt;

&lt;p&gt;The mistake is using the funnel as proof of cause. A funnel movement tells you something changed in behavior. It does not prove why.&lt;/p&gt;

&lt;p&gt;A useful review sentence sounds like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signup completion dropped after deploy 9f3c. PostHog shows the loss at company validation. Sentry shows new validation exceptions on the same route. Investigate the validation change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence uses product analytics for behavior and error monitoring for failure. It does not make either tool do the other's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not force error monitoring to explain product movement
&lt;/h2&gt;

&lt;p&gt;Sentry can show a clean release while a route still gets worse. A copy change can confuse users without throwing. A layout change can hide a button without causing a JavaScript exception. A pricing experiment can reduce upgrade completion with no runtime failure. An auth flow can become slower or more confusing before it breaks.&lt;/p&gt;

&lt;p&gt;In those cases, the absence of a Sentry incident is useful but not final. It narrows the investigation. It does not close the review.&lt;/p&gt;

&lt;p&gt;The route still needs product evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did users reach the same step?&lt;/li&gt;
&lt;li&gt;Did they complete the same action?&lt;/li&gt;
&lt;li&gt;Did the exposed audience change?&lt;/li&gt;
&lt;li&gt;Did a feature flag split the population?&lt;/li&gt;
&lt;li&gt;Did the traffic source change?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A deploy can be technically clean and commercially worse. That is the point of reviewing the path, not only the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a deploy review packet
&lt;/h2&gt;

&lt;p&gt;The practical answer is not another giant dashboard. It is a packet that keeps the route, release, and evidence together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Packet 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;Deploy and PR&lt;/td&gt;
&lt;td&gt;Prevents vague "after the release" debugging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route or path&lt;/td&gt;
&lt;td&gt;Keeps the review tied to a customer experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protected metric&lt;/td&gt;
&lt;td&gt;Defines what healthy means&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product evidence&lt;/td&gt;
&lt;td&gt;Shows behavior movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime evidence&lt;/td&gt;
&lt;td&gt;Shows failures, latency, traces, or replay context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traffic context&lt;/td&gt;
&lt;td&gt;Explains whether audience changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;Turns evidence into action&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The packet does not replace PostHog, Sentry, Vercel, or OpenTelemetry. It makes them answer the same release question.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you mark stable, watch, investigate, or rollback candidate?
&lt;/h2&gt;

&lt;p&gt;Use stable when the protected metric stays in range and companion evidence is quiet.&lt;/p&gt;

&lt;p&gt;Use watch when the path moved but the supporting evidence is mixed. Maybe traffic changed. Maybe the sample is thin. Maybe the movement is near the edge of normal variance. Watch is a real state because it prevents overreaction.&lt;/p&gt;

&lt;p&gt;Use investigate when the path moved and the deploy plausibly touched it. At that point the owner needs evidence links, not another meeting.&lt;/p&gt;

&lt;p&gt;Use rollback candidate when the route movement and technical evidence point to the same release. That still requires judgment, but the conversation becomes concrete: the release changed this path, the path got worse, and the evidence supports action. For a full walkthrough of that moment, see &lt;a href="https://dev.to/blog/the-deploy-shipped-the-route-got-worse-now-what"&gt;the deploy shipped, the route got worse, now what&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use TrueClara to keep the question intact
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/product"&gt;TrueClara&lt;/a&gt; is built for the space between product analytics and error monitoring. It does not ask a team to abandon either tool. It keeps deploy metadata, route ownership, path metrics, and evidence links attached to the release.&lt;/p&gt;

&lt;p&gt;That gives the team a release review object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what shipped&lt;/li&gt;
&lt;li&gt;what route it touched&lt;/li&gt;
&lt;li&gt;what behavior changed&lt;/li&gt;
&lt;li&gt;what PostHog showed&lt;/li&gt;
&lt;li&gt;what Sentry showed&lt;/li&gt;
&lt;li&gt;what Vercel or trace data explained&lt;/li&gt;
&lt;li&gt;what decision the team made&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostHog shows behavior. Sentry shows breakage. Vercel and OpenTelemetry add traffic and instrumentation context. TrueClara keeps the deploy question intact long enough for the team to answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who owns the release review?
&lt;/h2&gt;

&lt;p&gt;Tool choice is not the only reason deploy-impact reviews fail. Ownership is just as important.&lt;/p&gt;

&lt;p&gt;If product owns the funnel, engineering owns the errors, growth owns experiments, and platform owns deployments, the review can fall between teams. Everyone has a useful dashboard. Nobody owns the release question.&lt;/p&gt;

&lt;p&gt;Assign the owner by path, not by tool. If the deploy touched signup, the signup owner owns the review. If it touched checkout, the checkout owner owns the review. If it touched onboarding, the onboarding owner owns the review. Engineering still investigates technical evidence. Product still interprets behavior. The owner keeps the decision moving.&lt;/p&gt;

&lt;p&gt;A path owner should be able to say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this deploy touched my path&lt;/li&gt;
&lt;li&gt;this metric defines path health&lt;/li&gt;
&lt;li&gt;these evidence links matter&lt;/li&gt;
&lt;li&gt;this is stable, watch, investigate, or rollback candidate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That ownership model prevents the team from debating whether PostHog or Sentry is the "real" source. The real source is the review packet. The tools provide evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the answer is no action?
&lt;/h2&gt;

&lt;p&gt;A good deploy review often ends with no action. That is not wasted work.&lt;/p&gt;

&lt;p&gt;If the route stayed stable, the team learned that the release did not damage the protected path. If the route moved but traffic mix explains it, the team avoided a false incident. If product behavior changed but Sentry stayed quiet, the team learned to investigate UX or copy before chasing exceptions. If errors increased but the path stayed healthy, the team can prioritize cleanup without treating it as a product emergency.&lt;/p&gt;

&lt;p&gt;The value is not always a rollback. The value is a decision with evidence.&lt;/p&gt;

&lt;p&gt;That decision becomes part of release memory. The next time signup moves, the team can compare against prior reviews instead of starting from scratch. Over time, the route review becomes a habit: every important deploy leaves behind a short record of what happened to the customer path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog Next.js docs&lt;/a&gt; - Product analytics, feature flag, and event instrumentation for Next.js apps.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry for Next.js&lt;/a&gt; - Error monitoring, performance monitoring, tracing, replay, and release debugging for Next.js.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Analytics&lt;/a&gt; - Page-level visitor analytics for Vercel applications.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; - Official route, fetch, and span instrumentation guidance for Next.js.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>observability</category>
      <category>sentry</category>
      <category>devops</category>
    </item>
    <item>
      <title>CI Can Take Screenshots. It Should Not Publish Them Blindly.</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Thu, 09 Jul 2026 14:30:37 +0000</pubDate>
      <link>https://dev.to/jakexkim/ci-can-take-screenshots-it-should-not-publish-them-blindly-51bb</link>
      <guid>https://dev.to/jakexkim/ci-can-take-screenshots-it-should-not-publish-them-blindly-51bb</guid>
      <description>&lt;p&gt;CI is a good place to generate product screenshots. It is a dangerous place to publish them without review.&lt;/p&gt;

&lt;p&gt;A browser can open the product, load a fixture, wait for the UI, capture the page, and store the result as a build artifact. &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; supports page screenshots, full-page screenshots, element screenshots, and buffers. &lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; can run the job on pull requests, scheduled builds, or docs changes. Tools like &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper&lt;/a&gt; make website screenshot capture easy to script.&lt;/p&gt;

&lt;p&gt;The capture problem is manageable. The publishing problem is where teams get hurt.&lt;/p&gt;

&lt;p&gt;A CI screenshot can be technically fresh and still wrong for the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat screenshots like build artifacts
&lt;/h2&gt;

&lt;p&gt;A build artifact has provenance. It came from a commit, a workflow, an environment, and a set of inputs. A docs screenshot needs the same treatment.&lt;/p&gt;

&lt;p&gt;If a screenshot appears in public documentation, the team should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which commit generated it?&lt;/li&gt;
&lt;li&gt;Which route did it capture?&lt;/li&gt;
&lt;li&gt;Which fixture or account state was loaded?&lt;/li&gt;
&lt;li&gt;Which viewport and theme were used?&lt;/li&gt;
&lt;li&gt;Which selector proved the page was ready?&lt;/li&gt;
&lt;li&gt;Which docs page uses the asset?&lt;/li&gt;
&lt;li&gt;Who approved the replacement?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that trail, the screenshot is just a generated PNG. It may be correct. It may be a loading state with nice pixels.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact 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;Commit&lt;/td&gt;
&lt;td&gt;pull request head SHA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route&lt;/td&gt;
&lt;td&gt;\/settings\/api-keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixture&lt;/td&gt;
&lt;td&gt;team workspace with one active key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport&lt;/td&gt;
&lt;td&gt;1440x900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ready condition&lt;/td&gt;
&lt;td&gt;data-docs-ready="api-keys"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capture target&lt;/td&gt;
&lt;td&gt;API key table panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs usage&lt;/td&gt;
&lt;td&gt;docs\/api-keys\/create.mdx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review state&lt;/td&gt;
&lt;td&gt;approved, rejected, needs scenario fix&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This metadata is what makes CI screenshots safe to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate screenshot tests from docs screenshots
&lt;/h2&gt;

&lt;p&gt;Visual regression tests and documentation screenshots overlap, but they are not the same job.&lt;/p&gt;

&lt;p&gt;A visual regression test asks, "Did the UI change unexpectedly?" A docs screenshot asks, "Does this image still teach the product step correctly?"&lt;/p&gt;

&lt;p&gt;The same browser capture may support both jobs, but the review rules are different. A pixel difference can fail a regression test. In docs, a pixel difference may be the expected result of a product improvement. Conversely, a screenshot can pass a pixel threshold and still be bad documentation if it shows the wrong user state or crop.&lt;/p&gt;

&lt;p&gt;A clean CI design keeps the outputs separate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visual regression result&lt;/td&gt;
&lt;td&gt;Engineering&lt;/td&gt;
&lt;td&gt;Pass, fail, update baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs screenshot artifact&lt;/td&gt;
&lt;td&gt;Docs, product, or support owner&lt;/td&gt;
&lt;td&gt;Approve, reject, request scenario fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public docs asset&lt;/td&gt;
&lt;td&gt;Docs publisher&lt;/td&gt;
&lt;td&gt;Replace only after approval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That separation prevents engineering test baselines from becoming public documentation by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make product state deterministic enough
&lt;/h2&gt;

&lt;p&gt;Screenshot CI fails when the product state depends on live data, random accounts, timestamps, or whatever a developer had in their local workspace.&lt;/p&gt;

&lt;p&gt;The fix is not always a full fake backend. Start with the state that matters for the screenshot. If the screenshot teaches API key creation, the page needs a workspace with a known key state. If it teaches billing settings, the page needs a known plan and payment state. If it teaches integration setup, the page needs connected and disconnected variants.&lt;/p&gt;

&lt;p&gt;Useful controls include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seeded demo workspaces&lt;/li&gt;
&lt;li&gt;mocked network responses for unstable external data&lt;/li&gt;
&lt;li&gt;fixed dates and time zones&lt;/li&gt;
&lt;li&gt;explicit feature flag values&lt;/li&gt;
&lt;li&gt;test users with known roles&lt;/li&gt;
&lt;li&gt;data attributes for capture targets and ready states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to make the entire product deterministic. The goal is to make the screenshot state reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait for the UI you mean to capture
&lt;/h2&gt;

&lt;p&gt;A surprising number of bad screenshots are readiness bugs. The browser captured too early. A skeleton was still visible. A chart had not drawn. A toast covered the button. A font swapped after capture. A settings panel was still animating.&lt;/p&gt;

&lt;p&gt;The wait condition should be part of the scenario, not an invisible sleep.&lt;/p&gt;

&lt;p&gt;Weak:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wait 3000ms&lt;/li&gt;
&lt;li&gt;take full-page screenshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open route&lt;/li&gt;
&lt;li&gt;wait for account fixture to load&lt;/li&gt;
&lt;li&gt;wait for data-docs-ready="billing-settings"&lt;/li&gt;
&lt;li&gt;capture locator for the billing panel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Playwright gives teams the browser primitives. The scenario should express what "ready" means for the docs image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not auto-replace public docs on every run
&lt;/h2&gt;

&lt;p&gt;The safest default is: CI generates, humans approve, publishing replaces.&lt;/p&gt;

&lt;p&gt;Auto-replacement can work for low-risk internal references. It is risky for public product documentation, especially setup flows, compliance screens, billing pages, admin permissions, and integration guides. Those images carry trust. A wrong screenshot can make the docs look current while making the instruction worse.&lt;/p&gt;

&lt;p&gt;Use review states:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generated&lt;/td&gt;
&lt;td&gt;CI produced the screenshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed&lt;/td&gt;
&lt;td&gt;The artifact differs from the current docs asset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs review&lt;/td&gt;
&lt;td&gt;A human must approve replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approved&lt;/td&gt;
&lt;td&gt;The screenshot can publish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;The asset is wrong or docs need editing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scenario fix needed&lt;/td&gt;
&lt;td&gt;The automation captured the wrong state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives the team a lane for screenshots instead of a folder of files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Reshot between CI and the docs repo
&lt;/h2&gt;

&lt;p&gt;Reshot keeps the screenshot scenario, CI artifact, review decision, and approved docs asset together. It does not replace Playwright. It does not replace your CI provider. It gives the screenshots a workflow.&lt;/p&gt;

&lt;p&gt;A practical setup looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define scenarios for screenshots that matter.&lt;/li&gt;
&lt;li&gt;Run the scenarios in CI when product or docs inputs change.&lt;/li&gt;
&lt;li&gt;Store generated artifacts with commit, route, fixture, viewport, and docs usage.&lt;/li&gt;
&lt;li&gt;Send changed screenshots to review.&lt;/li&gt;
&lt;li&gt;Publish approved assets back to the docs repo or asset store.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how CI becomes useful for documentation screenshots without becoming reckless.&lt;/p&gt;

&lt;p&gt;The browser can take the picture. The team still needs to decide whether the picture belongs in the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name artifacts so people can debug them
&lt;/h2&gt;

&lt;p&gt;Screenshot artifacts often fail at the boring layer: naming. A folder called screenshots is not enough when a reviewer needs to know which product state produced an image.&lt;/p&gt;

&lt;p&gt;Use names that carry useful context without becoming unreadable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Naming part&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;Product area&lt;/td&gt;
&lt;td&gt;settings-api-keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scenario state&lt;/td&gt;
&lt;td&gt;one-active-key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport&lt;/td&gt;
&lt;td&gt;desktop-1440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme or locale&lt;/td&gt;
&lt;td&gt;light-en&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit or run id&lt;/td&gt;
&lt;td&gt;pr-1842&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A generated file like settings-api-keys_&lt;em&gt;one-active-key&lt;/em&gt;&lt;em&gt;desktop-1440&lt;/em&gt;_pr-1842.png is not beautiful, but it is debuggable. The reviewer can tell what it is before opening it. The CI logs, artifact store, and docs replacement step can all refer to the same object.&lt;/p&gt;

&lt;p&gt;Pair the file name with a small metadata record. The metadata should include the route, fixture, ready condition, capture target, docs page, and reviewer state. That turns a PNG into a traceable artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record common failure modes
&lt;/h2&gt;

&lt;p&gt;The first few CI screenshot failures will teach the team more than the happy path. Capture them as scenario fixes instead of one-off debugging.&lt;/p&gt;

&lt;p&gt;Common failure modes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app captured before hydration finished&lt;/li&gt;
&lt;li&gt;test account missing required plan or role&lt;/li&gt;
&lt;li&gt;modal or toast covering the target element&lt;/li&gt;
&lt;li&gt;external integration state unavailable&lt;/li&gt;
&lt;li&gt;browser viewport different from docs layout&lt;/li&gt;
&lt;li&gt;animation or chart rendering after capture&lt;/li&gt;
&lt;li&gt;feature flag exposing a different UI than production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each failure should improve the scenario. Add a better ready signal. Seed the fixture. Mock the unstable response. Capture the locator instead of the full page. Pin the feature flag. The point is not to make screenshots flawless immediately. It is to make the workflow more deterministic each time it fails.&lt;/p&gt;

&lt;p&gt;This is why review is so important. Review does not only protect the docs. It improves the screenshot system itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright screenshots&lt;/a&gt; - Official screenshot APIs for pages, locators, full-page captures, and buffers.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions docs&lt;/a&gt; - Workflow automation and artifacts in CI.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper docs&lt;/a&gt; - Scriptable website screenshot capture.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ci</category>
      <category>playwright</category>
      <category>screenshots</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Playwright Screenshots for Documentation: What to Automate and What to Review</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:30:37 +0000</pubDate>
      <link>https://dev.to/jakexkim/playwright-screenshots-for-documentation-what-to-automate-and-what-to-review-3h44</link>
      <guid>https://dev.to/jakexkim/playwright-screenshots-for-documentation-what-to-automate-and-what-to-review-3h44</guid>
      <description>&lt;p&gt;Playwright can take the screenshot. It cannot decide whether that screenshot still belongs in your docs.&lt;/p&gt;

&lt;p&gt;That distinction matters. &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright's screenshot guide&lt;/a&gt; covers the mechanics: full-page captures, element captures, buffers, clipping, image quality, and file output. Those primitives are exactly what a docs team needs to stop retaking the same product screenshot by hand.&lt;/p&gt;

&lt;p&gt;But documentation screenshots are not ordinary test artifacts. A product image in a setup guide is part of the instruction. It has to show the right user state, the right public UI, the right viewport, and enough surrounding context for a reader to complete a task. Automation helps only when it creates a reviewable artifact instead of a folder full of mystery PNGs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate repeatable product states first
&lt;/h2&gt;

&lt;p&gt;The best first candidates are screenshots where the product state is repeatable and the cost of staleness is high.&lt;/p&gt;

&lt;p&gt;Start with the screenshots that help users finish operational tasks: creating an API key, connecting an integration, changing a billing setting, configuring OAuth, exporting data, or checking an admin permission. These screenshots usually have a known route, a stable UI target, and a docs page that depends on them. They are much better candidates than launch collages, annotated marketing images, or one-off editorial visuals.&lt;/p&gt;

&lt;p&gt;Playwright's locator screenshot support is especially useful here because many docs images should show a panel, table, modal, or settings section rather than the entire browser window. Capturing a locator keeps the screenshot tied to the instructional object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the screenshot in a scenario manifest
&lt;/h2&gt;

&lt;p&gt;A Playwright test file can capture a page, but the docs team needs more than executable code. It needs a scenario record that explains why the screenshot exists.&lt;/p&gt;

&lt;p&gt;A useful scenario manifest contains:&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;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docs page&lt;/td&gt;
&lt;td&gt;docs/integrations/github.mdx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product route&lt;/td&gt;
&lt;td&gt;/settings/integrations/github&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixture&lt;/td&gt;
&lt;td&gt;workspace with GitHub connected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport&lt;/td&gt;
&lt;td&gt;desktop 1440x900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme&lt;/td&gt;
&lt;td&gt;light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ready signal&lt;/td&gt;
&lt;td&gt;data-docs-ready="github-connected"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capture target&lt;/td&gt;
&lt;td&gt;integration settings panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;static/img/integrations/github-connected.png&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;docs-platform&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This manifest is not bureaucracy. It is the maintenance contract. When the UI changes, the reviewer can see what state the screenshot was supposed to show. When capture fails, the engineer can tell whether the route, fixture, selector, or docs path is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Playwright for readiness, not sleeps
&lt;/h2&gt;

&lt;p&gt;Documentation screenshots often fail in a boring way: the browser captures too early.&lt;/p&gt;

&lt;p&gt;A skeleton is still visible. A chart has not rendered. A toast covers the button. A font swaps after the screenshot. A menu animation has not finished. The screenshot looks plausible, but it teaches the wrong thing.&lt;/p&gt;

&lt;p&gt;Do not hide readiness in a long timeout. Express it in the scenario. The runner should navigate to the route, select the fixture, wait for the loaded state that matters, confirm the target element is visible, and only then capture the intended locator.&lt;/p&gt;

&lt;p&gt;Playwright's docs for screenshots show how simple the capture call can be. The hard part is the sentence before it: "This page is now in the state the docs need."&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate visual tests from docs screenshots
&lt;/h2&gt;

&lt;p&gt;Playwright also supports &lt;a href="https://playwright.dev/docs/test-snapshots" rel="noopener noreferrer"&gt;visual comparisons&lt;/a&gt;, and teams often ask whether docs screenshots should just be visual tests.&lt;/p&gt;

&lt;p&gt;They overlap, but they are not the same workflow.&lt;/p&gt;

&lt;p&gt;A pixel diff is useful evidence, not the final decision. If a button moved because the product shipped a better layout, the docs screenshot probably should change. If the image changed because the capture account lost access to a feature, the screenshot should not publish.&lt;/p&gt;

&lt;p&gt;Visual comparison asks whether the UI changed unexpectedly. Documentation screenshot review asks whether the image still teaches the step correctly. The same browser capture can feed both workflows, but the decision record should be separate. Visual testing protects UI behavior. Screenshot review protects public instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store artifacts with enough context
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/tutorials/store-and-share-data" rel="noopener noreferrer"&gt;GitHub Actions artifacts&lt;/a&gt; are a good place to store generated screenshots during CI. The important part is naming and metadata.&lt;/p&gt;

&lt;p&gt;A screenshot artifact should answer basic questions without asking the reviewer to inspect CI logs. It needs the commit, scenario, route, fixture, viewport, theme, current docs asset, and whether the candidate differs from the approved version.&lt;/p&gt;

&lt;p&gt;Use names that are readable in a CI artifact list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;settings-api-keys__one-active-key__desktop-1440__light__pr-1842.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file name does not need to carry everything. Pair it with a small JSON record containing route, owner, ready signal, docs path, and review status. That turns a Playwright output file into a docs artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review what automation cannot know
&lt;/h2&gt;

&lt;p&gt;Automation can detect that an image changed. It cannot know whether the change is useful, public, or misleading.&lt;/p&gt;

&lt;p&gt;Review is where docs, product, support, and engineering have different but useful perspectives. Engineering can tell whether the UI state is valid. Docs can tell whether the screenshot still supports the surrounding copy. Support can spot the states users misunderstand. Product can confirm whether the flow is ready to describe publicly. If a banner, toast, wrong account, unreleased flag, or bad crop appears in the image, the reviewer catches it before the docs inherit the mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish only after approval
&lt;/h2&gt;

&lt;p&gt;A safe pipeline has two distinct jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate screenshots from Playwright scenarios.&lt;/li&gt;
&lt;li&gt;Publish approved docs assets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not collapse those jobs just because CI can write to the docs repo. A technically fresh screenshot can still be wrong. It can show a test account, a transient experiment, a loading state, or a feature that has not launched.&lt;/p&gt;

&lt;p&gt;Use review states, but keep the state model simple. A screenshot is generated, changed, needs review, approved, rejected, or blocked on a scenario fix. That is enough to keep CI fast and publishing conservative without turning the workflow into a status taxonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Reshot as the artifact layer
&lt;/h2&gt;

&lt;p&gt;Reshot fits between Playwright and the docs repository. Playwright handles browser automation. GitHub Actions or another CI system runs the job. Reshot keeps the scenario, generated screenshot, metadata, review decision, and approved output together.&lt;/p&gt;

&lt;p&gt;That lets teams automate the repeatable parts without losing editorial judgment. A practical Reshot setup defines scenarios for the screenshots that matter, runs Playwright when a scenario input changes, stores the generated image with metadata, routes changed screenshots into review, and publishes only the approved asset back to the docs path.&lt;/p&gt;

&lt;p&gt;Playwright is the capture engine. Reshot is the place where the capture becomes a documentation artifact with state, ownership, and a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright screenshots&lt;/a&gt; - Official screenshot primitives for page, full-page, buffer, and locator capture.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://playwright.dev/docs/test-snapshots" rel="noopener noreferrer"&gt;Playwright visual comparisons&lt;/a&gt; - Official guidance for snapshot and visual comparison testing.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://docs.github.com/en/actions/tutorials/store-and-share-data" rel="noopener noreferrer"&gt;GitHub Actions workflow artifacts&lt;/a&gt; - Official artifact upload, download, and retention guidance.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://github.com/simonw/shot-scraper" rel="noopener noreferrer"&gt;shot-scraper&lt;/a&gt; - Open source CLI project for automated website screenshots.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>playwright</category>
      <category>documentation</category>
      <category>screenshots</category>
      <category>ci</category>
    </item>
    <item>
      <title>Next.js Observability Is Not Deploy Impact</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:30:43 +0000</pubDate>
      <link>https://dev.to/jakexkim/nextjs-observability-is-not-deploy-impact-361j</link>
      <guid>https://dev.to/jakexkim/nextjs-observability-is-not-deploy-impact-361j</guid>
      <description>&lt;p&gt;Next.js observability tells you what the application is doing. Deploy impact tells you whether the release changed a product path that matters.&lt;/p&gt;

&lt;p&gt;Those are related questions, but they are not the same question.&lt;/p&gt;

&lt;p&gt;A team can have Sentry installed, OpenTelemetry spans flowing, Vercel Web Analytics enabled, Speed Insights collecting Core Web Vitals, and product analytics events in place. The deploy can still leave everyone asking the same awkward question: did this release hurt signup, checkout, onboarding, invite acceptance, or another route customers actually feel?&lt;/p&gt;

&lt;p&gt;The missing layer is not more raw signal. It is the review that ties signal to a deploy, a route, a protected metric, and a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability gives you witnesses, not the case file
&lt;/h2&gt;

&lt;p&gt;The official &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; describes instrumentation for route rendering, fetches, API routes, metadata generation, and custom spans. That is useful engineering evidence. It can tell you that a route got slower, a fetch changed shape, or a server action became noisier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/" rel="noopener noreferrer"&gt;Sentry's Next.js docs&lt;/a&gt; explain how to collect errors, performance data, replays, logs, and tracing context from a Next.js app. That is also useful evidence. It can tell you when a release introduced a new exception or made a transaction slower.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Web Analytics&lt;/a&gt; gives page and visitor behavior. &lt;a href="https://vercel.com/docs/speed-insights" rel="noopener noreferrer"&gt;Vercel Speed Insights&lt;/a&gt; helps watch real-user performance signals. &lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog's Next.js docs&lt;/a&gt; cover product events and feature flags.&lt;/p&gt;

&lt;p&gt;All of those tools answer important questions. Error monitoring can show a runtime failure. Tracing can show a route, fetch, or handler changing shape. Web analytics can show page traffic and visitor behavior. Product analytics can show funnel movement. Deployment metadata can show what shipped and when.&lt;/p&gt;

&lt;p&gt;None of that automatically decides whether the product path got worse because of the release. Deploy impact starts when those witnesses are attached to the same release question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deploy question is narrower than the observability question
&lt;/h2&gt;

&lt;p&gt;Observability asks: what is happening in the system?&lt;/p&gt;

&lt;p&gt;Deploy impact asks: after this deploy, did this product path get worse?&lt;/p&gt;

&lt;p&gt;That narrower question is why the review should start with a route, not a dashboard. If a pull request touched &lt;code&gt;/signup/company&lt;/code&gt;, the first review target is signup completion and the evidence around that path. If it touched payment state, the first review target is checkout completion and payment failure context. If it changed auth middleware, the first review target may be login success, invitation acceptance, and protected route access.&lt;/p&gt;

&lt;p&gt;The route keeps the review honest. Without it, the team can waste time scanning broad charts until one of them looks interesting. With it, the team can move in a straight line: anchor the question to a deploy, name the path a user could feel, compare the path against a health metric, then decide whether the companion evidence supports action.&lt;/p&gt;

&lt;p&gt;The answer may still be "no action." That is fine. A reviewed stable path is useful release memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  A green deploy is only a shipping fact
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/deployments" rel="noopener noreferrer"&gt;Vercel's deployment docs&lt;/a&gt; are clear that every successful build creates a deployment with its own URL, and Git pushes or pull requests can trigger those deployments. That is the shipping layer.&lt;/p&gt;

&lt;p&gt;The product layer starts after the deployment exists.&lt;/p&gt;

&lt;p&gt;A successful build tells you the app was assembled and deployed. It does not tell you whether users completed signup at the same rate. It does not tell you whether the pricing page sent fewer people to checkout. It does not tell you whether a validation change increased retries without throwing an exception.&lt;/p&gt;

&lt;p&gt;This distinction matters because a team can mistake platform health for product health. The build may pass while signup gets slower. Sentry may stay quiet while checkout completion dips. Web Vitals may stay normal while an onboarding step becomes confusing. Product analytics may show a dip that is later explained by traffic mix rather than code.&lt;/p&gt;

&lt;p&gt;The review should preserve both facts: the deploy was technically green, and the path still needs evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a deploy impact packet
&lt;/h2&gt;

&lt;p&gt;A deploy impact packet is small enough to become habit. It is not a postmortem. It is not a dashboard replacement. It is the minimum object that keeps the release question from scattering.&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;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Commit, deployment URL, shipped time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull request&lt;/td&gt;
&lt;td&gt;What changed and who owns it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/signup/company&lt;/code&gt;, &lt;code&gt;/checkout&lt;/code&gt;, &lt;code&gt;/onboarding/connect&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protected metric&lt;/td&gt;
&lt;td&gt;Completion, successful submit, activation, payment success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before window&lt;/td&gt;
&lt;td&gt;Comparable traffic before release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After window&lt;/td&gt;
&lt;td&gt;First stable traffic window after release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence links&lt;/td&gt;
&lt;td&gt;Sentry issue, trace sample, PostHog funnel, Vercel page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;Stable, watch, investigate, rollback candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The packet is deliberately boring. Boring is useful when a chart is moving and everyone wants to jump to a theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for the false comfort of complete instrumentation
&lt;/h2&gt;

&lt;p&gt;Instrumentation coverage can become its own trap. Once a team has enough traces, events, logs, and analytics, it is tempting to believe the answer is in there somewhere. It usually is. The problem is retrieval under pressure.&lt;/p&gt;

&lt;p&gt;If the team has not named the path, the search space is too large. If it has not named the deploy, every chart becomes a possible coincidence. If it has not named the decision state, the review ends as a Slack thread instead of an action.&lt;/p&gt;

&lt;p&gt;The decision language should stay plain. Stable means the protected metric stayed in range and evidence is quiet. Watch means the path moved, but traffic, sample size, or companion evidence is mixed. Investigate means the path moved and the deploy plausibly touched it. Rollback candidate means the path moved and technical evidence points to the same release.&lt;/p&gt;

&lt;p&gt;That standard does not require perfect causality. It requires enough evidence to choose the next action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what to review before the release
&lt;/h2&gt;

&lt;p&gt;The best post-deploy review starts in the pull request.&lt;/p&gt;

&lt;p&gt;If a PR touches a value route, add the post-deploy review target before merge: affected path &lt;code&gt;/signup/company&lt;/code&gt;, protected metric company signup completion, evidence to check in validation errors, signup funnel, and route traces, owner onboarding, review window first stable post-deploy traffic window. That short note changes the release habit. The team is no longer surprised after the chart moves. It already knows which path deserves attention.&lt;/p&gt;

&lt;p&gt;This is where TrueClara fits. It keeps the deploy, path, metric, evidence links, and decision together. Sentry remains the place to inspect errors and traces. PostHog remains the place to inspect events and funnels. Vercel remains the place to inspect deployments, page behavior, and performance. TrueClara keeps them attached to the product path the release may have changed.&lt;/p&gt;

&lt;p&gt;Observability tells you what the system emitted. Deploy impact tells you whether the release changed the path customers care about. Next.js teams need both, but they should not confuse one for the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; - Official Next.js guide for instrumentation, default spans, custom spans, testing, and deployment.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/" rel="noopener noreferrer"&gt;Sentry Next.js docs&lt;/a&gt; - Sentry setup and monitoring documentation for Next.js applications.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Web Analytics&lt;/a&gt; - Vercel documentation for page and visitor analytics.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://vercel.com/docs/speed-insights" rel="noopener noreferrer"&gt;Vercel Speed Insights&lt;/a&gt; - Vercel documentation for real-user performance measurements.&lt;/li&gt;
&lt;li&gt;[C5] &lt;a href="https://vercel.com/docs/deployments" rel="noopener noreferrer"&gt;Vercel deployments&lt;/a&gt; - Vercel deployment methods, URLs, environments, and deployment metadata.&lt;/li&gt;
&lt;li&gt;[C6] &lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog Next.js docs&lt;/a&gt; - PostHog setup guidance for Next.js, events, and feature flags.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>observability</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>AIF-C01 exam dumps: the real cost, and what to do instead</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Thu, 02 Jul 2026 14:30:31 +0000</pubDate>
      <link>https://dev.to/jakexkim/aif-c01-exam-dumps-the-real-cost-and-what-to-do-instead-9gd</link>
      <guid>https://dev.to/jakexkim/aif-c01-exam-dumps-the-real-cost-and-what-to-do-instead-9gd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short version:&lt;/strong&gt; if you're searching for AIF-C01 dumps, you're almost certainly not looking to cheat — you're looking to stop spinning your wheels and just pass. Fair. But dumps are a worse bet than they look: they risk your certification, they're often wrong, and they don't make you ready. The good news is the efficient path you actually want exists, and it's not much slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "exam dumps" really are
&lt;/h2&gt;

&lt;p&gt;A dump is a set of supposedly real exam questions, copied from people who recently sat the test and reposted on a forum or a paid site. Two problems are baked in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They're frequently wrong.&lt;/strong&gt; Dumped questions are transcribed from memory and "answered" by community vote. On foundational AI topics — where the right answer often hinges on a single AWS service detail — a confidently-upvoted wrong answer is worse than no answer at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They go stale fast.&lt;/strong&gt; AWS rotates exam forms, and AIF-C01 covers fast-moving ground (generative AI and foundation models are over half the blueprint). Last quarter's dump is studying for an exam that no longer exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cost that doesn't show up until later
&lt;/h2&gt;

&lt;p&gt;Using real exam content violates the &lt;strong&gt;AWS Certification agreement and NDA&lt;/strong&gt; you accept before every exam. AWS actively pursues this. The documented consequences include having your result invalidated, your certifications revoked, and a ban from future exams. A revoked cert on a background-checked résumé is a far bigger problem than an extra week of study.&lt;/p&gt;

&lt;p&gt;And there's the quieter cost: even if a dump "works," you walk into the job having memorized answers to questions you'll never be asked again, instead of understanding Bedrock, SageMaker, foundation models, and responsible-AI guidelines well enough to use them. The certification was supposed to be a proxy for that understanding. Dumps optimize away the only thing it was measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually want instead
&lt;/h2&gt;

&lt;p&gt;The honest reason people reach for dumps is they want &lt;strong&gt;certainty that they're ready&lt;/strong&gt; without grinding endlessly. That's a reasonable thing to want — it's just available without the risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Study from the blueprint, not from memory.&lt;/strong&gt; The public AIF-C01 exam guide lists every domain and task. Work the official AWS docs for each, weighted by exam share: Applications of Foundation Models (28%) and Generative AI fundamentals (24%) are more than half the exam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice on source-traced questions.&lt;/strong&gt; Use questions written from the blueprint and official docs, each with the source behind its answer — so when you get one wrong, you learn the real thing, not a forum's guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a readiness signal you can trust.&lt;/strong&gt; This is the part dumps are a broken substitute for. You don't want "I saw these questions before" — you want "my projected score clears 700 with margin." Those are different, and only one survives a fresh exam form.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How CutScore covers exactly that gap
&lt;/h2&gt;

&lt;p&gt;CutScore is built on a hard line: &lt;strong&gt;no brain-dump sources and no real exam content, ever&lt;/strong&gt; — only the public blueprint and official documentation. Every question is independently authored and source-linked. And the readiness number is deliberately conservative: it says "ready — book your exam" only when the &lt;em&gt;lower bound&lt;/em&gt; of your projected score clears the pass bar and you've covered every domain. It would rather show you no number than tell you you're ready when you aren't.&lt;/p&gt;

&lt;p&gt;That's the certainty you were looking for in a dump — minus the wrong answers and the risk to your certification.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Are AIF-C01 dumps illegal?&lt;/strong&gt; Using real exam content breaches the AWS Certification NDA you agree to before testing. AWS can invalidate results, revoke certifications, and ban you from future exams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aren't some "dumps" just practice questions?&lt;/strong&gt; Legitimate, independently-authored practice questions written from the public blueprint are fine and useful. The problem is specifically transcribed &lt;em&gt;real exam&lt;/em&gt; content — and many free dump sites mix the two without telling you which is which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it really take to pass AIF-C01 without dumps?&lt;/strong&gt; Most candidates with some cloud exposure need a few focused weeks. The bottleneck isn't volume — it's knowing when you've done enough, which is exactly what a calibrated readiness score answers.&lt;/p&gt;

&lt;p&gt;Skip the risk. &lt;a href="https://cutscore.dev" rel="noopener noreferrer"&gt;See where you actually stand with CutScore →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>The Deploy Shipped. The Route Got Worse. Now What?</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Wed, 01 Jul 2026 14:30:46 +0000</pubDate>
      <link>https://dev.to/jakexkim/the-deploy-shipped-the-route-got-worse-now-what-1189</link>
      <guid>https://dev.to/jakexkim/the-deploy-shipped-the-route-got-worse-now-what-1189</guid>
      <description>&lt;p&gt;A route regression after deploy is not an alert by itself. It is a question the team has to answer before the release fades into memory.&lt;/p&gt;

&lt;p&gt;The app stayed up. The deploy was green. Error volume did not explode. But a route that matters started behaving differently. Signup completion softened. Checkout abandonment rose. Invite acceptance dipped. Onboarding step two suddenly had more exits than usual. That is the gray zone where most teams lose time, because every tool has a piece of the story and none of them owns the decision.&lt;/p&gt;

&lt;p&gt;The fastest review is narrow: one deploy, one route, one path metric, one evidence packet, one decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the route, not the dashboard
&lt;/h2&gt;

&lt;p&gt;The first mistake is opening every dashboard before naming the thing that changed. A dashboard can tell you many things. It cannot decide which user path deserved protection from this release.&lt;/p&gt;

&lt;p&gt;A route regression review starts with a sentence like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy 9f3c changed company signup validation. Review \/signup\/company completion for the first stable post-deploy window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence has a deploy, a route, and a behavior. It is much stronger than "conversion looks down," because it tells an engineer where to look and tells a product owner what is at risk.&lt;/p&gt;

&lt;p&gt;In a Next.js application, the route can be literal or conceptual. It might be one page, one server action, a multi-step checkout flow, or the route group behind an integration setup. What matters is that the review maps the release to the customer path.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If the deploy touched&lt;/th&gt;
&lt;th&gt;Review first&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Signup form, validation, identity copy&lt;/td&gt;
&lt;td&gt;Signup completion and validation errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkout page, payment state, pricing logic&lt;/td&gt;
&lt;td&gt;Checkout completion and payment failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding steps, empty states, workspace creation&lt;/td&gt;
&lt;td&gt;Step completion and activation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth middleware, session handling, invitations&lt;/td&gt;
&lt;td&gt;Login success and invite acceptance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration setup, OAuth, API keys&lt;/td&gt;
&lt;td&gt;Successful connection and retry rate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Whole-product averages are useful later. They are weak at the start. A route can regress while the site-level chart looks normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze the release context while it is still fresh
&lt;/h2&gt;

&lt;p&gt;Every route review needs a small case file. Not a postmortem. Not a long incident document. Just the facts that make the question answerable tomorrow.&lt;/p&gt;

&lt;p&gt;Capture the deploy time, commit, pull request, author, touched route, owner, protected metric, before window, after window, and evidence links. This gives the team a shared object instead of a pile of tabs.&lt;/p&gt;

&lt;p&gt;The tools you already use matter here. &lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry for Next.js&lt;/a&gt; can show errors, traces, replays, and performance context. &lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog's Next.js docs&lt;/a&gt; show how product events and feature flags can be captured in a Next.js app. &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Analytics&lt;/a&gt; gives page-level visitor behavior. The &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; explains the instrumentation layer for route rendering, fetches, and spans.&lt;/p&gt;

&lt;p&gt;Those tools are witnesses. The case file is the review.&lt;/p&gt;

&lt;p&gt;A useful packet looks like this:&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;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;9f3c, June 26, 10:14 UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull request&lt;/td&gt;
&lt;td&gt;Changed signup validation and company field copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route&lt;/td&gt;
&lt;td&gt;\/signup\/company&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protected metric&lt;/td&gt;
&lt;td&gt;Company signup completion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before window&lt;/td&gt;
&lt;td&gt;Previous comparable traffic window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After window&lt;/td&gt;
&lt;td&gt;First stable traffic window after deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Sentry validation errors, product funnel, trace sample, route analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;Stable, watch, investigate, or rollback candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point is not bureaucracy. The point is making the next action obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the route against itself
&lt;/h2&gt;

&lt;p&gt;A route regression review should not begin by comparing the route to the whole product. It should compare the route against its own expected behavior.&lt;/p&gt;

&lt;p&gt;Ask four questions in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did the route move after the deploy?&lt;/li&gt;
&lt;li&gt;Did the deploy plausibly touch that route or the data behind it?&lt;/li&gt;
&lt;li&gt;Did companion evidence move in the same direction?&lt;/li&gt;
&lt;li&gt;Is the right decision stable, watch, investigate, or rollback candidate?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The companion evidence matters because timing is not causality. A traffic source can change. A campaign can start. A bot spike can hit the route. A feature flag can expose a different audience. A payment provider can have a bad hour. If completion dipped after deploy but errors, traces, traffic, and event mix do not support the same story, the right answer may be watch.&lt;/p&gt;

&lt;p&gt;But if completion dipped, the PR touched the same path, Sentry shows a new validation exception, and replay samples show users stuck on the same step, the review has enough evidence to become an investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use decision states that do not require a meeting
&lt;/h2&gt;

&lt;p&gt;The review should end in a state. Not a vibe. Not a paragraph of hedging. Four states are enough.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;The route stayed within expected range&lt;/td&gt;
&lt;td&gt;Record the review and move on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watch&lt;/td&gt;
&lt;td&gt;The route moved but evidence is mixed or traffic is thin&lt;/td&gt;
&lt;td&gt;Recheck after more traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigate&lt;/td&gt;
&lt;td&gt;The route moved and the deploy plausibly touched it&lt;/td&gt;
&lt;td&gt;Assign an owner and inspect linked evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback candidate&lt;/td&gt;
&lt;td&gt;The route moved and telemetry points to failure on the same path&lt;/td&gt;
&lt;td&gt;Prepare rollback or hotfix discussion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Watch" is not weakness. It prevents teams from pretending every chart movement is a release failure. "Rollback candidate" is also not panic. It says the evidence is strong enough to justify a serious rollback conversation.&lt;/p&gt;

&lt;p&gt;The discipline is to record the state while the context is still attached to the deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the note someone can act on
&lt;/h2&gt;

&lt;p&gt;A good route review note is short and specific:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy 9f3c changed company signup validation. \/signup\/company completion dropped in the first stable post-deploy window. Sentry shows new validation errors on the same route. Marked investigate. Owner: onboarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That note can live in a release log, a PR comment, an incident timeline, or a Slack thread. It gives the next person enough to act without reconstructing the whole story.&lt;/p&gt;

&lt;p&gt;A weak note looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signup looks weird after deploy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not a review. It is a hint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what to review before the next deploy
&lt;/h2&gt;

&lt;p&gt;The strongest route regression workflow starts before the release. If a pull request touches checkout validation, the review target is checkout completion. If it touches onboarding copy, the review target is onboarding step completion. If it touches auth middleware, the review target might be login success and invite acceptance.&lt;/p&gt;

&lt;p&gt;A pre-deploy note can be tiny:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route touched: \/signup\/company&lt;/li&gt;
&lt;li&gt;Protected metric: company signup completion&lt;/li&gt;
&lt;li&gt;Evidence to check: validation errors, signup funnel, route traces&lt;/li&gt;
&lt;li&gt;Owner: onboarding&lt;/li&gt;
&lt;li&gt;Review window: first stable post-deploy traffic window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That note changes the post-deploy conversation. Instead of asking "what happened?" the team asks "did the protected path stay healthy?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Use TrueClara for the case file
&lt;/h2&gt;

&lt;p&gt;TrueClara keeps the deploy, route, metric, evidence links, and decision together. Sentry remains where you inspect runtime failures. PostHog remains where you inspect events and funnels. Vercel remains where you inspect page behavior. OpenTelemetry remains where you inspect request-level detail.&lt;/p&gt;

&lt;p&gt;The missing layer is the review object that ties those signals to the release.&lt;/p&gt;

&lt;p&gt;When the next deploy ships, do not start with every dashboard. Start with the route the customer feels. If it stayed healthy, record that and move on. If it moved, attach the evidence. If the evidence points to the release, investigate. If the same path is failing, make the rollback conversation concrete.&lt;/p&gt;

&lt;p&gt;A green deploy tells you the release shipped. A route review tells you whether the path survived it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as enough evidence
&lt;/h2&gt;

&lt;p&gt;A route review does not need courtroom certainty. It needs enough evidence to choose the next action.&lt;/p&gt;

&lt;p&gt;For a stable decision, the protected metric stayed in range and no companion evidence contradicts it. For watch, the route moved but the sample is thin, the traffic mix changed, or the supporting signals disagree. For investigate, the route moved and the deploy plausibly touched the path. For rollback candidate, the route moved and technical evidence points at the same release.&lt;/p&gt;

&lt;p&gt;That standard keeps the team out of two bad extremes. One extreme is overreacting to every chart movement. The other is waiting for perfect proof while a critical path keeps leaking users.&lt;/p&gt;

&lt;p&gt;Use evidence in layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence layer&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;Path metric&lt;/td&gt;
&lt;td&gt;Whether the customer action moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy metadata&lt;/td&gt;
&lt;td&gt;Whether the release plausibly touched the path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime signal&lt;/td&gt;
&lt;td&gt;Whether errors, latency, or traces changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior signal&lt;/td&gt;
&lt;td&gt;Whether users dropped, retried, or changed flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traffic context&lt;/td&gt;
&lt;td&gt;Whether the audience changed enough to explain it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decision is strongest when multiple layers point in the same direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put route review into the pull request habit
&lt;/h2&gt;

&lt;p&gt;Post-deploy review gets easier when the pull request names the path before the release lands.&lt;/p&gt;

&lt;p&gt;A useful PR note is short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;affected path: \/signup\/company&lt;/li&gt;
&lt;li&gt;protected metric: signup completion&lt;/li&gt;
&lt;li&gt;likely evidence: validation errors, route traces, signup funnel&lt;/li&gt;
&lt;li&gt;review owner: onboarding&lt;/li&gt;
&lt;li&gt;expected review window: first stable post-deploy traffic window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That note helps reviewers see whether the PR touches a critical path. It also gives the post-deploy reviewer a starting point. Nobody has to guess what mattered after the chart changes.&lt;/p&gt;

&lt;p&gt;The PR does not need to predict the outcome. It needs to name the path that deserves attention. TrueClara uses that path to keep the release review focused after deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry for Next.js&lt;/a&gt; - Next.js error monitoring, tracing, replay, and performance context.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://posthog.com/docs/libraries/next-js" rel="noopener noreferrer"&gt;PostHog Next.js docs&lt;/a&gt; - Product analytics, events, and feature flag implementation for Next.js.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Analytics&lt;/a&gt; - Page and visitor analytics for Vercel-hosted applications.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; - Official instrumentation guide for Next.js routes, fetches, and spans.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>observability</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Stop Doing Screenshot Sweeps Before Every Launch</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Wed, 01 Jul 2026 14:30:37 +0000</pubDate>
      <link>https://dev.to/jakexkim/stop-doing-screenshot-sweeps-before-every-launch-2a7g</link>
      <guid>https://dev.to/jakexkim/stop-doing-screenshot-sweeps-before-every-launch-2a7g</guid>
      <description>&lt;p&gt;A screenshot sweep feels productive until it becomes the release ritual everyone dreads.&lt;/p&gt;

&lt;p&gt;Someone opens every docs page. Someone compares the current UI with screenshots from three months ago. Someone asks which workspace has the right data. Someone retakes a settings page. Someone crops it differently than last time. Someone notices the nav changed in another article. Launch week turns into a scavenger hunt for stale images.&lt;/p&gt;

&lt;p&gt;The problem is not that teams forget screenshots. The problem is that screenshots are not connected to the product changes that make them stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale screenshots are visual debt
&lt;/h2&gt;

&lt;p&gt;Visual debt accumulates whenever a product image no longer matches the current product experience. It is quiet at first. One button label changes. A sidebar moves. An empty state gets redesigned. A plan name changes. The docs still mostly work, so nobody stops the release.&lt;/p&gt;

&lt;p&gt;Then support tickets get slower because users do not see what the docs show. Sales engineers stop trusting setup guides. Product marketing avoids screenshots because they might be wrong. Writers spend launch week auditing images instead of improving the page.&lt;/p&gt;

&lt;p&gt;The debt exists because a screenshot usually has no owner, no scenario, and no trigger.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screenshot problem&lt;/th&gt;
&lt;th&gt;Operational cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Old UI appears in docs&lt;/td&gt;
&lt;td&gt;No link between UI change and docs asset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong account state&lt;/td&gt;
&lt;td&gt;Fixture is implicit or manually recreated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inconsistent crop&lt;/td&gt;
&lt;td&gt;Capture target is not defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale mobile image&lt;/td&gt;
&lt;td&gt;Viewport was not part of the scenario&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken screenshot in release week&lt;/td&gt;
&lt;td&gt;Review happens after the product ships&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix is not a heroic sweep. The fix is to turn important screenshots into maintained scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  A screenshot needs a trigger
&lt;/h2&gt;

&lt;p&gt;A docs screenshot should regenerate when the product area it represents changes. That does not mean every commit should rebuild every screenshot. It means each scenario should know what it depends on.&lt;/p&gt;

&lt;p&gt;A setup screenshot might depend on the integration settings page, the connected-state fixture, and the docs file where the image appears. A billing screenshot might depend on the billing route, plan fixtures, and the page that explains invoices. A dashboard screenshot might depend on a demo workspace fixture and a set of chart components.&lt;/p&gt;

&lt;p&gt;When one of those inputs changes, the screenshot becomes suspect.&lt;/p&gt;

&lt;p&gt;A useful trigger model is simple:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change detected&lt;/th&gt;
&lt;th&gt;Screenshot action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docs page changed but product route did not&lt;/td&gt;
&lt;td&gt;Rebuild linked screenshots for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product route changed but docs page did not&lt;/td&gt;
&lt;td&gt;Rebuild affected screenshots and flag docs owners&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixture changed&lt;/td&gt;
&lt;td&gt;Rebuild scenarios using that fixture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport or capture target changed&lt;/td&gt;
&lt;td&gt;Require review before publishing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only unrelated code changed&lt;/td&gt;
&lt;td&gt;Skip screenshot run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This keeps automation focused. The team reviews screenshots when the product or docs created a reason to review them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use browser automation for capture, not judgment
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; are excellent at capturing pages, full pages, and locators. &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper&lt;/a&gt; is useful when a team wants a CLI-first screenshot workflow. &lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; can run the capture job in CI and store artifacts.&lt;/p&gt;

&lt;p&gt;Those pieces handle capture and execution. They do not decide whether an image still teaches the right thing.&lt;/p&gt;

&lt;p&gt;That decision belongs to review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the new UI the public experience?&lt;/li&gt;
&lt;li&gt;Does the screenshot match the step in the docs?&lt;/li&gt;
&lt;li&gt;Did the automation capture a loading state?&lt;/li&gt;
&lt;li&gt;Did a banner, modal, or test account artifact appear?&lt;/li&gt;
&lt;li&gt;Does the docs copy need to change with the image?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A changed screenshot is not always bad. Sometimes the product changed correctly. The review exists so the right image publishes with the right explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario is the maintenance contract
&lt;/h2&gt;

&lt;p&gt;A scenario is a small contract between the product and the docs.&lt;/p&gt;

&lt;p&gt;It says: to produce this image, open this route, set this state, use this viewport, wait for this element, capture this target, and ask this owner before publishing changes.&lt;/p&gt;

&lt;p&gt;That contract removes the guesswork from screenshot maintenance.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario 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;Docs page&lt;/td&gt;
&lt;td&gt;docs\/integrations\/github.mdx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product route&lt;/td&gt;
&lt;td&gt;\/settings\/integrations\/github&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixture&lt;/td&gt;
&lt;td&gt;workspace with GitHub connected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport&lt;/td&gt;
&lt;td&gt;1440x900 desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wait condition&lt;/td&gt;
&lt;td&gt;data-docs-ready="github-connected"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capture target&lt;/td&gt;
&lt;td&gt;integration settings panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;docs-platform&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scenario does not need to be elaborate. It needs to be explicit enough that the screenshot can be regenerated without asking a teammate which account they used last time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a review lane, not a screenshot dump
&lt;/h2&gt;

&lt;p&gt;The common failure mode is a CI job that drops a folder of PNGs somewhere. That is better than manual capture, but it still asks humans to infer what changed.&lt;/p&gt;

&lt;p&gt;A review lane should show the scenario metadata with the image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screenshot name&lt;/li&gt;
&lt;li&gt;docs page where it appears&lt;/li&gt;
&lt;li&gt;product route captured&lt;/li&gt;
&lt;li&gt;fixture and viewport&lt;/li&gt;
&lt;li&gt;old asset and new asset&lt;/li&gt;
&lt;li&gt;changed reason&lt;/li&gt;
&lt;li&gt;reviewer decision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reviewer should be able to approve, reject, or request a scenario fix. Rejection matters. Sometimes the screenshot is stale because the docs need updating. Sometimes it is wrong because the scenario captured the wrong state. Sometimes the product changed and the docs asset should not publish until the release is public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the publishing path conservative
&lt;/h2&gt;

&lt;p&gt;Fast generation is good. Conservative publishing is better.&lt;/p&gt;

&lt;p&gt;Do not let screenshot automation silently replace public docs assets unless the screenshot category is truly low risk. For product setup flows, billing pages, security settings, admin permissions, and integration states, review should be required.&lt;/p&gt;

&lt;p&gt;A practical policy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screenshot type&lt;/th&gt;
&lt;th&gt;Publish policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decorative marketing image&lt;/td&gt;
&lt;td&gt;Manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product setup step&lt;/td&gt;
&lt;td&gt;Required review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security or admin setting&lt;/td&gt;
&lt;td&gt;Required review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated reference image&lt;/td&gt;
&lt;td&gt;Review unless scenario is stable and low risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test-only internal screenshot&lt;/td&gt;
&lt;td&gt;Auto-upload as artifact, never publish&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This policy keeps automation from becoming a source of polished mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Reshot to remove the sweep
&lt;/h2&gt;

&lt;p&gt;Reshot is built for the job between browser capture and docs publishing. It keeps scenarios, generated screenshots, review status, and approved assets together.&lt;/p&gt;

&lt;p&gt;The workflow becomes predictable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A product or docs change touches a scenario input.&lt;/li&gt;
&lt;li&gt;CI runs the relevant screenshot scenarios.&lt;/li&gt;
&lt;li&gt;Reshot stores the generated artifacts with scenario context.&lt;/li&gt;
&lt;li&gt;Reviewers approve the screenshots that still teach the right thing.&lt;/li&gt;
&lt;li&gt;Approved assets replace the docs images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is how teams stop doing launch-week screenshot sweeps. They still review screenshots, but they review the ones that changed for a reason, with enough context to make a decision.&lt;/p&gt;

&lt;p&gt;The goal is not to eliminate human taste from documentation. The goal is to stop using human memory as the screenshot system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do one inventory, then stop doing sweeps
&lt;/h2&gt;

&lt;p&gt;There is still value in one manual inventory. The point is to make it the last broad sweep, not the new habit.&lt;/p&gt;

&lt;p&gt;Start by listing every screenshot in the docs and assigning it a maintenance status:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scenario candidate&lt;/td&gt;
&lt;td&gt;Product state is repeatable and useful to automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editorial asset&lt;/td&gt;
&lt;td&gt;Keep manual, usually launch or narrative imagery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove&lt;/td&gt;
&lt;td&gt;Screenshot no longer teaches enough to justify maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replace with text&lt;/td&gt;
&lt;td&gt;The step is clearer without an image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs product fixture&lt;/td&gt;
&lt;td&gt;Valuable, but state is not deterministic yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This inventory usually reveals that fewer screenshots deserve automation than expected. Some images can be deleted. Some can become text. Some need better product fixtures before capture makes sense. The remaining set becomes the scenario backlog.&lt;/p&gt;

&lt;p&gt;After that, stop sweeping the whole docs set before each launch. Review only screenshots connected to changed docs, changed product routes, changed fixtures, or changed scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make screenshot ownership boring
&lt;/h2&gt;

&lt;p&gt;A screenshot without an owner becomes everyone's problem and nobody's responsibility.&lt;/p&gt;

&lt;p&gt;Ownership does not need to be complicated. Each scenario needs one accountable group: docs platform, product marketing, developer relations, support, product team, or sales engineering. The owner does not have to take every screenshot manually. They decide whether a generated change can publish.&lt;/p&gt;

&lt;p&gt;Good ownership answers three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who reviews changes to this screenshot?&lt;/li&gt;
&lt;li&gt;Who fixes the scenario when capture breaks?&lt;/li&gt;
&lt;li&gt;Who updates the docs text if the UI changed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When those answers are visible, stale screenshots stop being surprise work during launch week. They become small review tasks attached to the product changes that created them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright screenshots&lt;/a&gt; - Browser screenshot primitives for pages, full pages, and elements.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper docs&lt;/a&gt; - CLI workflow for automated website screenshots.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions docs&lt;/a&gt; - CI workflows and artifact handling.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>documentation</category>
      <category>screenshots</category>
      <category>automation</category>
    </item>
    <item>
      <title>Your Docs Screenshot Is a Build Artifact Now</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Tue, 30 Jun 2026 14:30:37 +0000</pubDate>
      <link>https://dev.to/jakexkim/your-docs-screenshot-is-a-build-artifact-now-4eo3</link>
      <guid>https://dev.to/jakexkim/your-docs-screenshot-is-a-build-artifact-now-4eo3</guid>
      <description>&lt;p&gt;A documentation screenshot should not be a loose image someone remembers to update before launch.&lt;/p&gt;

&lt;p&gt;It should have a source route, a known state, a viewport, a wait condition, an owner, and a review step before it replaces the public docs asset. That is the difference between taking screenshots and operating screenshots.&lt;/p&gt;

&lt;p&gt;Teams already know the pain. The product UI changes. The docs screenshot still shows the old nav. A modal moved. A settings page got renamed. A dark-mode screenshot came from the wrong account. Someone notices during release week, opens the product, creates a fake workspace, crops an image by hand, uploads it to the docs repo, and hopes no other screenshot is stale.&lt;/p&gt;

&lt;p&gt;That workflow does not scale with a product that ships every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation starts with a scenario, not a screenshot
&lt;/h2&gt;

&lt;p&gt;Browser automation can capture a page. That part is solved. &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright's screenshot docs&lt;/a&gt; show the core primitives: page screenshots, full-page screenshots, element screenshots, buffers, clipping, quality, and format control. &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper&lt;/a&gt; turns screenshots into a developer-friendly CLI workflow, and Simon Willison's launch post names one of the most practical uses: keeping screenshots in documentation up to date.&lt;/p&gt;

&lt;p&gt;Those tools answer "can a browser capture this?"&lt;/p&gt;

&lt;p&gt;Documentation teams need the next layer: "can we regenerate the right screenshot, in the right state, and know whether it is safe to publish?"&lt;/p&gt;

&lt;p&gt;That starts with a scenario.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario 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;Route&lt;/td&gt;
&lt;td&gt;The product page, docs page, or local preview to capture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Signed-in user, plan, fixture data, language, theme, feature flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewport&lt;/td&gt;
&lt;td&gt;Desktop, mobile, retina, narrow panel, docs embed size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ready condition&lt;/td&gt;
&lt;td&gt;The selector, network state, or app signal that proves the UI is ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capture target&lt;/td&gt;
&lt;td&gt;Full page, component, locator, or clipped region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output path&lt;/td&gt;
&lt;td&gt;Where the generated asset lands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;Who approves replacement in docs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A screenshot without a scenario is a file. A screenshot with a scenario is an artifact that can be rebuilt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick one screenshot users depend on
&lt;/h2&gt;

&lt;p&gt;Do not start by automating every image in the docs. That usually creates a brittle pile of scripts and fixtures nobody wants to maintain.&lt;/p&gt;

&lt;p&gt;Start with one screenshot that creates support burden when it goes stale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API key creation&lt;/li&gt;
&lt;li&gt;billing settings&lt;/li&gt;
&lt;li&gt;OAuth connection&lt;/li&gt;
&lt;li&gt;workspace onboarding&lt;/li&gt;
&lt;li&gt;integration setup&lt;/li&gt;
&lt;li&gt;permission management&lt;/li&gt;
&lt;li&gt;dashboard empty state&lt;/li&gt;
&lt;li&gt;export configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first scenario should be plain. Open the route. Load deterministic data. Wait for the ready element. Capture the region. Upload the artifact. Ask for review if the output changed.&lt;/p&gt;

&lt;p&gt;That is enough to prove whether the workflow belongs in your docs pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The capture job is not the publishing job
&lt;/h2&gt;

&lt;p&gt;CI can generate screenshots quickly. Publishing should be slower.&lt;/p&gt;

&lt;p&gt;A generated screenshot can be wrong for many reasons. The fixture did not load. The user was not signed in. The app showed a cookie banner. The route was half-hydrated. The local server was on the wrong build. A feature flag changed the UI. A loading skeleton got captured. The browser viewport did not match the docs layout.&lt;/p&gt;

&lt;p&gt;Automatically publishing that image is worse than leaving a stale screenshot for a day, because now the docs have a fresh-looking lie.&lt;/p&gt;

&lt;p&gt;A safer workflow separates capture from approval:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect a docs, UI, or scenario change.&lt;/li&gt;
&lt;li&gt;Run the screenshot scenarios in CI.&lt;/li&gt;
&lt;li&gt;Store generated artifacts with the build.&lt;/li&gt;
&lt;li&gt;Compare against the current docs asset.&lt;/li&gt;
&lt;li&gt;Mark changed screenshots for review.&lt;/li&gt;
&lt;li&gt;Publish only after approval.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; is enough to run this kind of workflow. The missing piece is usually not CI. It is the review object that tells the team what changed and whether the screenshot can replace the docs image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots fail when product state is implicit
&lt;/h2&gt;

&lt;p&gt;Most screenshot automation breaks because state is hidden in a human's head.&lt;/p&gt;

&lt;p&gt;A person knows which account to use. A script does not. A person knows to dismiss the upgrade modal. A script does not. A person knows the screenshot should show the billing page after a card is attached. A script does not unless the scenario says so.&lt;/p&gt;

&lt;p&gt;Make state explicit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hidden assumption&lt;/th&gt;
&lt;th&gt;Scenario version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Use the demo workspace"&lt;/td&gt;
&lt;td&gt;workspace fixture: demo-docs-workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Make sure billing is active"&lt;/td&gt;
&lt;td&gt;plan: team, payment_status: active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Show the integration connected"&lt;/td&gt;
&lt;td&gt;integration: github_connected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Use the new nav"&lt;/td&gt;
&lt;td&gt;feature flag: nav_2026_on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Crop the settings panel"&lt;/td&gt;
&lt;td&gt;capture target: locator('[data-docs-shot="settings-panel"]')&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal is not perfect determinism on day one. The goal is to remove enough ambiguity that the screenshot can run again next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good documentation screenshots need reviewable diffs
&lt;/h2&gt;

&lt;p&gt;Pixel diffing is useful, but docs screenshots are not visual regression tests in the strict sense. A changed screenshot is not automatically a failure. It may be the point of the product change.&lt;/p&gt;

&lt;p&gt;The review question is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the scenario capture the intended state?&lt;/li&gt;
&lt;li&gt;Does the screenshot still teach the step in the docs?&lt;/li&gt;
&lt;li&gt;Did the crop, viewport, or density change accidentally?&lt;/li&gt;
&lt;li&gt;Is the new UI public and ready to document?&lt;/li&gt;
&lt;li&gt;Should the docs text change with the image?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why Reshot treats the screenshot as a docs artifact, not just a test output. The artifact needs enough context for a writer, product marketer, engineer, or support lead to approve it without rerunning the whole product in their head.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario file becomes the source of truth
&lt;/h2&gt;

&lt;p&gt;A strong docs screenshot system eventually looks like this:&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;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scenario manifest&lt;/td&gt;
&lt;td&gt;Defines route, state, viewport, wait condition, and capture target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser runner&lt;/td&gt;
&lt;td&gt;Uses Playwright or another browser engine to capture the asset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI workflow&lt;/td&gt;
&lt;td&gt;Runs scenarios on relevant changes and stores artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review queue&lt;/td&gt;
&lt;td&gt;Shows changed assets with scenario metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs repository&lt;/td&gt;
&lt;td&gt;Receives approved screenshots only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That architecture is simple on purpose. It lets teams keep Playwright, GitHub Actions, Docusaurus, VitePress, Mintlify, MkDocs, or a custom docs stack. The screenshot workflow sits beside the docs, not inside every writer's muscle memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Reshot for the reviewable artifact
&lt;/h2&gt;

&lt;p&gt;Reshot turns documentation screenshots into rebuildable, reviewable assets. Playwright can still capture the browser. GitHub Actions can still run the job. Your docs platform can still publish the final image.&lt;/p&gt;

&lt;p&gt;Reshot keeps the scenario, generated artifact, review state, and approved output together.&lt;/p&gt;

&lt;p&gt;That matters because documentation screenshots fail in the handoff. Engineers can write capture scripts. Writers can spot stale screenshots. Support can tell which steps confuse users. But without a shared artifact, every screenshot update becomes a one-off task.&lt;/p&gt;

&lt;p&gt;The practical standard is simple: if a screenshot teaches a product step, it deserves a scenario. If a scenario changes, the screenshot deserves a review. If the screenshot passes review, it becomes the docs asset.&lt;/p&gt;

&lt;p&gt;A screenshot is no longer a file someone drags into the repo. It is a build artifact with a reason to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what should never be automated
&lt;/h2&gt;

&lt;p&gt;Not every image in documentation deserves a scenario. Some screenshots are better treated as editorial assets, especially when they are composed, annotated, or designed for a launch narrative rather than a repeatable product state.&lt;/p&gt;

&lt;p&gt;Automate the images that teach stable product behavior. Be more careful with images that depend on taste, timing, or campaign context.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image type&lt;/th&gt;
&lt;th&gt;Automation fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup step screenshot&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings page state&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration connected or disconnected state&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard with seeded data&lt;/td&gt;
&lt;td&gt;Good fit if fixture is stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch marketing collage&lt;/td&gt;
&lt;td&gt;Weak fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annotated editorial image&lt;/td&gt;
&lt;td&gt;Review manually or generate from a controlled source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer-specific example&lt;/td&gt;
&lt;td&gt;Avoid unless anonymized and approved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This keeps the system from becoming heavy. The goal is not to automate every visual. The goal is to remove manual drift from the screenshots users rely on to complete work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep docs text and screenshots in the same review
&lt;/h2&gt;

&lt;p&gt;A screenshot change often means the text around it needs to change. A button label changes from Create key to Generate key. A setup page moves from Settings to Integrations. A modal becomes a side panel. If the image updates but the instructions do not, the docs still feel stale.&lt;/p&gt;

&lt;p&gt;The review should show the docs page where the screenshot appears. That lets the reviewer ask the right question: does this image still support the surrounding instruction?&lt;/p&gt;

&lt;p&gt;A strong review packet includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old screenshot&lt;/li&gt;
&lt;li&gt;generated screenshot&lt;/li&gt;
&lt;li&gt;scenario metadata&lt;/li&gt;
&lt;li&gt;docs page path&lt;/li&gt;
&lt;li&gt;nearby heading or step text&lt;/li&gt;
&lt;li&gt;product route&lt;/li&gt;
&lt;li&gt;reviewer decision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the difference between image replacement and documentation maintenance. Reshot belongs in that maintenance loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://playwright.dev/docs/screenshots" rel="noopener noreferrer"&gt;Playwright screenshots&lt;/a&gt; - Official documentation for page, full-page, buffer, and element screenshots.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://shot-scraper.datasette.io/" rel="noopener noreferrer"&gt;shot-scraper docs&lt;/a&gt; - CLI documentation for automated website screenshots.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://simonwillison.net/2022/Mar/10/shot-scraper/" rel="noopener noreferrer"&gt;shot-scraper launch post&lt;/a&gt; - First-party explanation of screenshot automation for documentation use cases.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://docs.github.com/actions" rel="noopener noreferrer"&gt;GitHub Actions docs&lt;/a&gt; - CI workflow and artifact automation reference.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>documentation</category>
      <category>screenshots</category>
      <category>playwright</category>
      <category>ci</category>
    </item>
    <item>
      <title>Your Next.js Deploy Was Green. Did Signup Get Worse?</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Mon, 29 Jun 2026 14:31:08 +0000</pubDate>
      <link>https://dev.to/jakexkim/your-nextjs-deploy-was-green-did-signup-get-worse-5hf6</link>
      <guid>https://dev.to/jakexkim/your-nextjs-deploy-was-green-did-signup-get-worse-5hf6</guid>
      <description>&lt;p&gt;A deploy can pass every technical check and still make the product worse.&lt;/p&gt;

&lt;p&gt;The build succeeds. The Vercel deployment is green. Sentry is not on fire. Nobody is in incident mode. Then the next morning signup looks softer, checkout has a weird dip, or onboarding completion is lower than it should be. The release did not break the app. It may have bent a path that matters.&lt;/p&gt;

&lt;p&gt;That is the review most Next.js teams do too late. They look at errors when the question is behavior. They look at a funnel when the missing context is the deploy. They look at page traffic when the path that matters is a user action. The answer is usually spread across Sentry, PostHog, Vercel Analytics, traces, logs, and the pull request.&lt;/p&gt;

&lt;p&gt;TrueClara is built around a narrower question: after this deploy, what happened to the route we care about?&lt;/p&gt;

&lt;p&gt;Not the whole site. Not every metric. One release. One critical path. Enough evidence to decide whether to ignore it, watch it, investigate it, or roll it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the path a customer feels
&lt;/h2&gt;

&lt;p&gt;A “Next.js deploy regression” is rarely a clean red line. It often shows up as hesitation in one path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer users finish &lt;code&gt;/signup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;more users abandon &lt;code&gt;/checkout&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;onboarding completion slips after a server action change&lt;/li&gt;
&lt;li&gt;invitation acceptance falls after a layout refactor&lt;/li&gt;
&lt;li&gt;API key creation succeeds less often after an auth update&lt;/li&gt;
&lt;li&gt;upgrade clicks are stable, but upgrade completion is not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are product-path questions. They are not the same as “is the app up?”&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; is useful because it shows how deeply Next.js can be instrumented: route rendering, fetches, API routes, metadata generation, and custom spans. That is the raw material. It does not tell you which route deserved a business review after deploy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry’s Next.js page&lt;/a&gt; is useful because it is concrete about error monitoring, performance, tracing, and replays. It gives you runtime evidence. It does not, by itself, decide whether the release made signup worse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Web Analytics&lt;/a&gt; is useful for page-level behavior. Product analytics tools are useful for funnels and events. None of those are the review. They are witnesses.&lt;/p&gt;

&lt;p&gt;The review starts when you name the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong first move is opening five dashboards
&lt;/h2&gt;

&lt;p&gt;When a release feels suspicious, the natural move is dashboard-hopping. Open Sentry. Open analytics. Open logs. Open the PR. Ask in Slack whether anyone changed the route. Compare screenshots in your head. Try to remember when the deploy went out.&lt;/p&gt;

&lt;p&gt;That works once. It does not become an operating habit.&lt;/p&gt;

&lt;p&gt;A better review starts with a small packet:&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;What goes in it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;commit, PR, deploy time, author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;the route or flow being protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path metric&lt;/td&gt;
&lt;td&gt;completion, submit success, abandonment, activation, upgrade, invite accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before window&lt;/td&gt;
&lt;td&gt;a comparable window before deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After window&lt;/td&gt;
&lt;td&gt;the first stable window after deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence links&lt;/td&gt;
&lt;td&gt;Sentry issue, PostHog funnel, Vercel page, trace, replay, log query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;stable, watch, investigate, rollback candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That packet is not a replacement for your tools. It is the case file that tells you which tools matter for this release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use each tool for the evidence it is good at
&lt;/h2&gt;

&lt;p&gt;Sentry is where you look when the route movement smells like a runtime problem: new exceptions, slower spans, broken server actions, hydration problems, or replays showing users stuck.&lt;/p&gt;

&lt;p&gt;PostHog, Amplitude, or another product analytics tool is where you look when the question is behavioral: did users complete the action at the same rate, did a step lose more people, did a feature flag change who saw the new path?&lt;/p&gt;

&lt;p&gt;Vercel Analytics is where you check whether traffic or page behavior changed enough to explain the movement. Sometimes the route did not get worse; the audience changed.&lt;/p&gt;

&lt;p&gt;OpenTelemetry is where you go when the path needs instrumentation-level explanation. If a route render got slower, a fetch started timing out, or a backend call moved, traces can turn a vague dip into something an engineer can inspect.&lt;/p&gt;

&lt;p&gt;TrueClara keeps those signals attached to the deploy and the path, so the review does not dissolve into tabs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  Deploy[Deploy] --&amp;gt; Path[Critical path]
  Path --&amp;gt; Metric[Path metric]
  Metric --&amp;gt; Evidence[Evidence links]
  Evidence --&amp;gt; Decision[Stable / Watch / Investigate / Rollback]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compare the path against itself
&lt;/h2&gt;

&lt;p&gt;Do not start with whole-site conversion. Do not start with all traffic. Do not average the problem away.&lt;/p&gt;

&lt;p&gt;If the deploy touched signup, review signup. If it touched checkout, review checkout. If it touched onboarding, review onboarding. Use the deploy timestamp as the anchor, then compare the path against a similar window before the release.&lt;/p&gt;

&lt;p&gt;A clean review asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the protected path move after deploy?&lt;/li&gt;
&lt;li&gt;Did the release touch code, copy, data, auth, payment, or routing on that path?&lt;/li&gt;
&lt;li&gt;Did traffic mix change enough to explain the movement?&lt;/li&gt;
&lt;li&gt;Do errors, traces, replays, or logs point at the same path?&lt;/li&gt;
&lt;li&gt;Is this stable, watch, investigate, or rollback candidate?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last line matters. A graph without a decision is just another tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision language should be boring
&lt;/h2&gt;

&lt;p&gt;Use four states.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stable&lt;/strong&gt; means the path stayed within the expected range. Record it and move on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch&lt;/strong&gt; means the path moved, but the evidence is mixed. Maybe traffic changed. Maybe a campaign started. Maybe the first post-deploy window is too thin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigate&lt;/strong&gt; means the path moved and the deploy plausibly touched it. Assign an owner. Open the evidence links. Keep the review tied to the path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollback candidate&lt;/strong&gt; means the path moved and companion evidence points to the same release. That still does not mean panic. It means the rollback or hotfix conversation is justified.&lt;/p&gt;

&lt;p&gt;Here is the kind of note a team can actually use:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy &lt;code&gt;9f3c&lt;/code&gt; touched signup validation. &lt;code&gt;/signup/company&lt;/code&gt; completion dropped in the first post-deploy window. Sentry shows new validation errors on the same route. Marked investigate; owner is onboarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here is the kind of note that wastes everyone’s time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Conversion looks down after the deploy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One is a review. The other is a weather report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PR Impact Preview should show before the deploy lands
&lt;/h2&gt;

&lt;p&gt;The best time to decide what to review is before the deploy ships.&lt;/p&gt;

&lt;p&gt;A PR that touches a critical path should say which route it affects and what evidence will be checked after release. If a pull request changes checkout validation, the review target is checkout completion. If it changes onboarding copy, the review target is onboarding step completion. If it changes auth middleware, the review target might be login success or invite acceptance.&lt;/p&gt;

&lt;p&gt;That is the point of PR Impact Preview in TrueClara: make the path explicit before the release, then keep the post-deploy review attached to that path.&lt;/p&gt;

&lt;p&gt;A useful preview is short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this PR touches &lt;code&gt;/signup/company&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;protected metric: company signup completion&lt;/li&gt;
&lt;li&gt;companion evidence: Sentry validation errors, signup funnel, route traces&lt;/li&gt;
&lt;li&gt;owner: onboarding&lt;/li&gt;
&lt;li&gt;review window: first stable post-deploy traffic window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview does not need to predict the future. It needs to prevent the team from deciding what mattered only after the chart looks strange.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the evidence lies
&lt;/h2&gt;

&lt;p&gt;Deploy review gets dangerous when it confuses timing with causality.&lt;/p&gt;

&lt;p&gt;A deploy can coincide with a campaign, a pricing change, a bot spike, a feature flag rollout, a backend incident, or a day-of-week pattern. A signup dip after deploy is not automatically caused by the deploy. A checkout drop with no errors is not automatically harmless.&lt;/p&gt;

&lt;p&gt;The review packet should make uncertainty visible. That is why “watch” exists. It is also why every decision should carry evidence links instead of a confident sentence with no trail.&lt;/p&gt;

&lt;p&gt;The goal is not to prove causality from one chart. The goal is to make the next action obvious enough that the team stops guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do on the next deploy
&lt;/h2&gt;

&lt;p&gt;Pick three paths before the next release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the path that makes money&lt;/li&gt;
&lt;li&gt;the path that activates new users&lt;/li&gt;
&lt;li&gt;the path that creates the most trust risk when broken&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each path, define one metric that means the path still works. Do not build a giant observability project. Do not instrument everything before reviewing anything. Start with the path where a bad deploy would hurt.&lt;/p&gt;

&lt;p&gt;After the next release, create the packet. If the path is stable, save the review. If it moves, attach the evidence. If the evidence points to the release, investigate. If the evidence points to failure on the same path, start the rollback conversation.&lt;/p&gt;

&lt;p&gt;A green deploy is not the same thing as a healthy product path. Treat the path as the thing you review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[C1] &lt;a href="https://nextjs.org/docs/app/guides/open-telemetry" rel="noopener noreferrer"&gt;Next.js OpenTelemetry guide&lt;/a&gt; - Official Next.js guide for instrumentation, default spans, custom spans, testing, and deployment.&lt;/li&gt;
&lt;li&gt;[C2] &lt;a href="https://sentry.io/for/nextjs/" rel="noopener noreferrer"&gt;Sentry for Next.js&lt;/a&gt; - Next.js monitoring page covering error monitoring, performance, tracing, replays, and release-adjacent debugging.&lt;/li&gt;
&lt;li&gt;[C3] &lt;a href="https://vercel.com/docs/analytics" rel="noopener noreferrer"&gt;Vercel Web Analytics&lt;/a&gt; - Official Vercel docs for page-level analytics and visitor behavior.&lt;/li&gt;
&lt;li&gt;[C4] &lt;a href="https://nurbak.com/en/blog/best-nextjs-monitoring-tools-2026/" rel="noopener noreferrer"&gt;Nurbak: Best Next.js monitoring tools in 2026&lt;/a&gt; - Current comparison-led Next.js monitoring article.&lt;/li&gt;
&lt;li&gt;[C5] &lt;a href="https://developers.google.com/search/docs/appearance/ai-features" rel="noopener noreferrer"&gt;Google: AI features and your website&lt;/a&gt; - Search guidance for crawlable, text-rich, snippet-eligible pages that can appear in AI features.&lt;/li&gt;
&lt;li&gt;[C6] &lt;a href="https://developers.google.com/search/docs/fundamentals/using-gen-ai-content" rel="noopener noreferrer"&gt;Google: using generative AI content&lt;/a&gt; - Guidance on AI-assisted content and scaled-content abuse risk.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>observability</category>
      <category>monitoring</category>
      <category>devops</category>
    </item>
    <item>
      <title>What Is Visual Debt (and Why Your Docs Are Lying to Users)</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:30:50 +0000</pubDate>
      <link>https://dev.to/jakexkim/what-is-visual-debt-and-why-your-docs-are-lying-to-users-cjg</link>
      <guid>https://dev.to/jakexkim/what-is-visual-debt-and-why-your-docs-are-lying-to-users-cjg</guid>
      <description>&lt;p&gt;&lt;strong&gt;Visual debt&lt;/strong&gt; is the accumulated gap between a product's current interface and the screenshots in its documentation. It compounds with every UI change that is not reflected in docs, eroding user trust and generating support burden. Unlike technical debt, visual debt is experienced directly by end users — and most teams have no mechanism to pay it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The definition: visual debt in one sentence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Visual debt&lt;/strong&gt; is the difference between what your product looks like right now and what your documentation shows. Every screenshot that depicts a button, layout, or workflow that no longer matches the live product adds to this balance. The debt accrues interest in the form of confused users, misguided support tickets, and lost trust in your documentation as a reliable source of truth.&lt;/p&gt;

&lt;p&gt;The term is modeled deliberately on &lt;strong&gt;technical debt&lt;/strong&gt;, which Ward Cunningham coined at the OOPSLA 1992 conference to describe the cost of shipping code that reflected an incomplete understanding of the problem domain. Cunningham's insight was that expedient choices in code create a compounding cost — and the longer you wait to address them, the more expensive the fix becomes. Visual debt operates on the same principle, but the interest is paid by your users, not your developers.&lt;/p&gt;

&lt;p&gt;I started using this term after auditing documentation for several SaaS products and finding that the visual state of the docs consistently lagged the product by &lt;strong&gt;two to four releases&lt;/strong&gt;. The text was updated, the API references were current, but the screenshots showed UIs that no longer existed. Every time a user followed a screenshot to a button that had moved or been renamed, that was visual debt collecting interest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The analogy to technical debt is intentional
&lt;/h2&gt;

&lt;p&gt;The power of Cunningham's original technical debt metaphor was that it gave engineering and business teams a shared language. The reason "technical debt" works as a concept is that it reframes an engineering problem in financial terms that executives understand.&lt;/p&gt;

&lt;p&gt;Visual debt borrows this framing because the same communication gap exists around documentation quality. When an engineer says "our screenshots are outdated," the response is typically a shrug — it is not perceived as urgent. When they say "we have 80 hours of visual debt and it is generating 15% of our support tickets," the conversation changes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Technical debt&lt;/th&gt;
&lt;th&gt;Visual debt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coined&lt;/td&gt;
&lt;td&gt;Ward Cunningham, OOPSLA 1992&lt;/td&gt;
&lt;td&gt;Introduced here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Definition&lt;/td&gt;
&lt;td&gt;Cost of expedient code choices&lt;/td&gt;
&lt;td&gt;Gap between current UI and docs screenshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who pays interest&lt;/td&gt;
&lt;td&gt;Developers (slower feature velocity)&lt;/td&gt;
&lt;td&gt;End users (confusion, failed tasks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compounds via&lt;/td&gt;
&lt;td&gt;Code complexity over time&lt;/td&gt;
&lt;td&gt;UI changes per release cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detected by&lt;/td&gt;
&lt;td&gt;Linters, code reviews, static analysis&lt;/td&gt;
&lt;td&gt;Nothing (manual audit only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid down by&lt;/td&gt;
&lt;td&gt;Refactoring, rewriting&lt;/td&gt;
&lt;td&gt;Re-capturing screenshots (manual or automated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracking tools&lt;/td&gt;
&lt;td&gt;SonarQube, CodeClimate, Snyk&lt;/td&gt;
&lt;td&gt;None widely adopted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The critical row in that table is "Detected by." Technical debt, for all its challenges, has an ecosystem of tools that surface it. Visual debt has nothing. No linter flags a three-release-old PNG. No CI check compares a screenshot against the live UI. This detection gap is why visual debt accumulates silently until a user files a support ticket saying "your docs show a settings page that does not exist."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the conventional advice is wrong
&lt;/h2&gt;

&lt;p&gt;The standard technical writing guidance on documentation screenshots falls into two camps, both wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Use fewer screenshots"&lt;/strong&gt; — This is surrender, not strategy. Screenshots are the single most effective way to orient a user in a UI. Removing them to avoid maintenance cost means your documentation is less useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Keep screenshots updated"&lt;/strong&gt; — This is advice with no mechanism. It is equivalent to saying "have fewer bugs." Without a system that automatically captures and refreshes screenshots, telling teams to keep them updated produces nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason both camps of advice fail is that they treat visual debt as a discipline problem when it is actually an infrastructure problem. Teams do not fail to update screenshots because they are lazy — they fail because the process is manual, unowned, and disconnected from the release cycle. Camunda's engineering team reported that their documentation user guide contained 94 screenshots, and that manually recreating all of them for a single release would take one to two full days. Their solution was to automate capture through end-to-end tests — they treated it as infrastructure, not process.&lt;/p&gt;

&lt;h2&gt;
  
  
  How visual debt compounds
&lt;/h2&gt;

&lt;p&gt;Visual debt does not grow linearly — it compounds. Each release introduces UI changes. Some are cosmetic (a button color change, an icon swap). Some are structural (a page reorganization, a feature rename). Every change that touches a UI element visible in a screenshot creates visual debt. But unlike code changes, where a failing test immediately surfaces the problem, screenshot staleness triggers &lt;strong&gt;no automated alert&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The compounding effect works across three dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Breadth&lt;/strong&gt; — a single header redesign can invalidate dozens of screenshots simultaneously. When Camunda changed their header, every screenshot containing a header was instantly stale. With 94 screenshots, even a minor navigation change creates a cascading update burden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth&lt;/strong&gt; — screenshots accumulate over time. A product that ships a release every two weeks with five new features will add 5–15 new screenshots per quarter. If none are being retired, the maintenance surface area grows every sprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Velocity&lt;/strong&gt; — faster release cycles mean faster visual debt accumulation. A product releasing biweekly at a Series B/C stage can realistically invalidate &lt;strong&gt;10–20% of its documentation screenshots per quarter&lt;/strong&gt; through normal UI iteration. Without automated recapture, the debt balance grows monotonically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a typical scenario — a product releasing every two weeks with 8–12% of screenshots affected per release and no automated recapture — &lt;strong&gt;over half of documentation screenshots are visually inaccurate within five releases&lt;/strong&gt;. That is roughly two and a half months. At that point, screenshots are not documentation — they are disinformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure your visual debt balance
&lt;/h2&gt;

&lt;p&gt;Visual debt can be quantified. Here is the formula I use:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Visual debt balance (hours)&lt;/strong&gt; = total screenshots × staleness rate × minutes per manual update ÷ 60&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total screenshots&lt;/strong&gt; — count every screenshot in your docs, including variants (mobile, dark mode). A typical B2B SaaS product has 50–200.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staleness rate&lt;/strong&gt; — the percentage of screenshots that no longer accurately reflect the current UI. Audit manually or estimate based on releases since last docs refresh. If you have not updated screenshots in three releases, assume 25–40%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minutes per manual update&lt;/strong&gt; — time to navigate to the right state, capture, crop, annotate, export, commit, and deploy. Manual capture typically takes &lt;strong&gt;10–15 minutes&lt;/strong&gt; per screenshot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A worked example: a product with &lt;strong&gt;120 screenshots&lt;/strong&gt;, of which &lt;strong&gt;35%&lt;/strong&gt; are stale (42 screenshots), each requiring &lt;strong&gt;12 minutes&lt;/strong&gt; of manual effort to update. The visual debt balance is 42 × 12 ÷ 60 = &lt;strong&gt;8.4 engineering hours&lt;/strong&gt;. At a fully loaded rate of $75/hour, that is &lt;strong&gt;$630&lt;/strong&gt; of accumulated visual debt. Multiply by 4 quarterly releases, and you are looking at $2,520/year of recurring manual cost — assuming you actually do the work. Most teams do not, and the debt simply grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only way to pay it down
&lt;/h2&gt;

&lt;p&gt;Manual screenshot maintenance is the documentation equivalent of manual deployments — it works at small scale and fails structurally as the product grows. The only sustainable way to reach and maintain a visual debt balance of zero is to automate documentation screenshots through CI.&lt;/p&gt;

&lt;p&gt;The mechanism has three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declarative screenshot definitions&lt;/strong&gt; — define what to capture (URL, selector, viewport, wait condition) in a config file that lives in your repo alongside documentation source files. The config is the single source of truth, not a scattered set of PNGs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline-triggered capture&lt;/strong&gt; — wire the capture step into your existing CI pipeline so it runs on every deploy, every PR to main, or on a schedule. This is what Camunda did with their end-to-end test suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable delivery URLs&lt;/strong&gt; — serve screenshots through CDN URLs that always resolve to the latest capture. This decouples screenshot freshness from docs deployment — the documentation page is never redeployed, but its images are always current. This is the step that separates a zero-visual-debt architecture from one that merely reduces the manual effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The outcome: visual debt drops to zero and stays there. Every &lt;code&gt;git push&lt;/code&gt; to main triggers a recapture. Every docs page serves the latest screenshot. No human intervention required.&lt;/p&gt;

&lt;p&gt;For the full picture of what visual debt costs and how to architect around it, see &lt;a href="https://dev.to/visual-debt"&gt;what visual debt actually costs your team&lt;/a&gt; and &lt;a href="https://dev.to/blog/screenshots-as-code"&gt;screenshots as code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is visual debt?&lt;/strong&gt;&lt;br&gt;
Visual debt is the accumulated difference between a product's current user interface and the screenshots shown in its documentation. Every UI change that is not reflected in docs screenshots increases the visual debt balance. Unlike text-based documentation errors, visual debt is immediately apparent to users — a screenshot showing a button that no longer exists cannot be skimmed past.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is visual debt different from technical debt?&lt;/strong&gt;&lt;br&gt;
Technical debt, coined by Ward Cunningham in 1992, describes the cost of expedient code choices. Visual debt describes the cost of expedient documentation choices. The key difference is who experiences the interest: technical debt slows down developers, while visual debt directly confuses end users, generating support tickets and eroding trust in the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you measure visual debt?&lt;/strong&gt;&lt;br&gt;
Count the total screenshots in your documentation, multiply by the percentage that no longer accurately reflect the current UI, and multiply by the average time to manually update each one. The result is your visual debt balance expressed in engineering hours. A product with 100 screenshots where 40% are stale and each takes 12 minutes to update carries 8 hours of visual debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do teams accumulate visual debt?&lt;/strong&gt;&lt;br&gt;
Visual debt accumulates because screenshot maintenance is entirely manual in most workflows. No CI/CD pipeline catches a stale screenshot. No linter flags a PNG that was last modified three releases ago. The task of updating screenshots has no natural owner — developers consider it a docs problem, and technical writers consider it a product problem. Without tooling that automates the capture-and-update cycle, visual debt is structurally inevitable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you pay down visual debt?&lt;/strong&gt;&lt;br&gt;
The only sustainable way to pay down visual debt is to automate screenshot capture through CI/CD pipelines. Define screenshot targets in a config file, trigger capture on every deploy, and serve images through stable CDN URLs that always resolve to the latest version. This eliminates the manual cycle that causes visual debt to accumulate in the first place.&lt;/p&gt;

</description>
      <category>screenshots</category>
      <category>automation</category>
      <category>documentation</category>
      <category>ci</category>
    </item>
    <item>
      <title>Screenshots as Code for Docs</title>
      <dc:creator>jake</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:30:48 +0000</pubDate>
      <link>https://dev.to/jakexkim/screenshots-as-code-for-docs-4hfl</link>
      <guid>https://dev.to/jakexkim/screenshots-as-code-for-docs-4hfl</guid>
      <description>&lt;h1&gt;
  
  
  Screenshots as Code: Automating Documentation Visuals
&lt;/h1&gt;

&lt;p&gt;In 2016, the idea of defining your server infrastructure in a YAML file and committing it to git felt radical. Today, infrastructure as code is the default. Nobody manually configures production servers through a web console anymore.&lt;/p&gt;

&lt;p&gt;Documentation screenshots are stuck in the 2015 era. Someone opens the product, manually navigates to the right screen, takes a screenshot, crops it, annotates it, and drops it into a docs folder. When the UI changes, someone (maybe the same person, maybe not) has to repeat the entire process. There is no version control, no automation, and no way to know whether the screenshots in your docs still match reality.&lt;/p&gt;

&lt;p&gt;The screenshots-as-code approach applies the same principles that transformed infrastructure management to documentation visuals. Define what you want to capture in configuration. Let automation handle the execution. Integrate it into your existing workflows. Version everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Infrastructure as Code Analogy
&lt;/h2&gt;

&lt;p&gt;The parallels are direct:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Infrastructure as Code&lt;/th&gt;
&lt;th&gt;Screenshots as Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terraform/Pulumi config files&lt;/td&gt;
&lt;td&gt;Screenshot capture config files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;terraform plan&lt;/code&gt; (preview changes)&lt;/td&gt;
&lt;td&gt;Visual diff (preview screenshot changes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;terraform apply&lt;/code&gt; (deploy infra)&lt;/td&gt;
&lt;td&gt;Capture and publish (deploy screenshots)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State file (current infra state)&lt;/td&gt;
&lt;td&gt;Visual registry (current screenshot state)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift detection (config vs. actual)&lt;/td&gt;
&lt;td&gt;Visual debt detection (screenshot vs. live UI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-environment (staging, prod)&lt;/td&gt;
&lt;td&gt;Multi-variant (themes, locales, roles)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mental model is the same: your documentation visuals should be a &lt;strong&gt;deterministic output&lt;/strong&gt; of a declarative configuration, not a manual artifact managed through tribal knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Config-Driven Screenshot Capture
&lt;/h2&gt;

&lt;p&gt;The foundation of screenshots-as-code is a configuration file that declaratively defines every screenshot your documentation needs. Here is what that looks like in practice:&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;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storageStatePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".reshot/auth-state.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&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;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;800&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;"scenarios"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dashboard-overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dashboard overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Main dashboard with sample data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readySelector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[data-ready='true']"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"steps"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waitForSelector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".dashboard-container"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dashboard-overview"&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;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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"settings-notifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Settings notifications tab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Settings page with notifications tab active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/settings/general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"steps"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[data-tab='notifications']"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"settings-notifications"&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;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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project-create-modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New project modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New project creation modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"steps"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[data-action='new-project']"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waitForSelector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".modal-overlay"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project-create-modal"&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;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="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;Each scenario entry defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; to navigate (&lt;code&gt;url&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How&lt;/strong&gt; to get there (&lt;code&gt;steps&lt;/code&gt; -- &lt;code&gt;goto&lt;/code&gt;, &lt;code&gt;click&lt;/code&gt;, &lt;code&gt;wait&lt;/code&gt;, &lt;code&gt;waitForSelector&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt; needed to reach the target state)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; to capture (the &lt;code&gt;screenshot&lt;/code&gt; step and its stable &lt;code&gt;key&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt; it exists (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The configuration is the source of truth. If a screenshot is not in the config, it should not be in your docs. If a step references a selector that no longer exists, the capture fails and you get an immediate signal that something changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps: Reaching Complex States
&lt;/h3&gt;

&lt;p&gt;Real documentation screenshots are rarely just "navigate to a URL and take a picture." You need to open modals, switch tabs, fill in sample data, expand accordions, hover over tooltips. The &lt;code&gt;steps&lt;/code&gt; array handles this:&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-upgrade-flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billing upgrade flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/settings/billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Upgrade modal with Pro plan selected and coupon applied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[data-action='upgrade']"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waitForSelector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".plan-comparison"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[data-plan='pro']"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#coupon-code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEMO2026"&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;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-upgrade-flow"&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;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;This is reproducible. Any engineer can read this config and understand exactly what the screenshot should show. There is no ambiguity, no "I think Sarah took this screenshot last quarter."&lt;/p&gt;

&lt;h2&gt;
  
  
  Variant Management
&lt;/h2&gt;

&lt;p&gt;This is where screenshots-as-code delivers its biggest advantage over manual processes. Modern products have multiple visual states, and your documentation should reflect all of them.&lt;/p&gt;

&lt;p&gt;Variants are declared once under a top-level &lt;code&gt;variants.dimensions&lt;/code&gt; block, then opted into per scenario with &lt;code&gt;"variants": { "dimensions": ["theme", "locale"] }&lt;/code&gt;. Each option lists how to set the state with an &lt;code&gt;inject&lt;/code&gt; array (&lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;cookie&lt;/code&gt;, or &lt;code&gt;browser&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Theme Variants
&lt;/h3&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;"variants"&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;"dimensions"&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;"theme"&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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"light"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Light Mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app-theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light"&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;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"colorScheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light"&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;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"dark"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dark Mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app-theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dark"&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;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"colorScheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dark"&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;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="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="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;h3&gt;
  
  
  Locale Variants
&lt;/h3&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;"variants"&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;"dimensions"&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;"locale"&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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Locale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"en"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"English"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&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;span class="nl"&gt;"ja"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Japanese"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ja"&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;span class="nl"&gt;"de"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"German"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"de"&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;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="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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Role Variants
&lt;/h3&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;"variants"&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;"dimensions"&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;"role"&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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"admin"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${ADMIN_TOKEN}"&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;span class="nl"&gt;"member"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Member"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${MEMBER_TOKEN}"&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;span class="nl"&gt;"viewer"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Viewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inject"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${VIEWER_TOKEN}"&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;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="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="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;When dimensions are opted into on a scenario, a single scenario definition produces multiple output images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs/images/
  dashboard-overview/
    light-en-admin.png
    light-en-member.png
    light-ja-admin.png
    dark-en-admin.png
    dark-en-member.png
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A documentation set with 50 scenarios and three variant dimensions (2 themes, 3 locales, 3 roles) produces &lt;strong&gt;900 screenshots&lt;/strong&gt; from 50 scenario entries. Doing this manually is not just tedious -- it is practically impossible to maintain. With config-driven capture, adding a new locale means adding one entry to the variants block and re-running automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Automation
&lt;/h2&gt;

&lt;p&gt;Screenshots-as-code reaches its full potential when integrated into your local workflow or any automation system. Here is a practical example using a shell script or build tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Visual Documentation Sync&lt;/span&gt;

&lt;span class="c"&gt;# Run this script on code changes or on a schedule (e.g., weekly)&lt;/span&gt;

npm run build
npm start &amp;amp;
&lt;span class="nv"&gt;APP_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
npx wait-on http://localhost:3000 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 60000

&lt;span class="c"&gt;# Capture against localhost, diff against the live registry, and push any&lt;/span&gt;
&lt;span class="c"&gt;# changed captures to the Reshot review queue. One command, no diff plumbing.&lt;/span&gt;
npx @reshotdev/screenshot run &lt;span class="nt"&gt;--headless&lt;/span&gt; &lt;span class="nt"&gt;--config&lt;/span&gt; reshot.config.json

&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$APP_PID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can integrate this into your build system, run it locally before commits, or script it into any workflow you prefer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Automation Does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Triggers on UI changes.&lt;/strong&gt; Re-capture screenshots whenever frontend code changes. No manual intervention needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runs on a schedule.&lt;/strong&gt; Set up a cron job or scheduled task to run weekly and catch changes that might slip through -- data-driven UI changes, third-party widget updates, or content changes from a CMS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Diffs against the live registry.&lt;/strong&gt; &lt;code&gt;reshot run&lt;/code&gt; compares each freshly captured screenshot against the current approved version Reshot is already serving. Anti-aliasing and rendering variance are tuned out, so you only hear about real changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sends changed captures to Review.&lt;/strong&gt; Changed screenshots land in the Reshot review queue as candidates, with the old version, the new version, and the visual diff side-by-side -- not as a pile of binary files in a git PR for someone to eyeball.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Review is the checkpoint. A reviewer approves a candidate and the &lt;strong&gt;same durable URL&lt;/strong&gt; Reshot was already serving flips to the new image -- every doc, README, and embed updates at once, with no find-and-replace and no broken links.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration with Existing Workflows
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The goal is not to add a new process. It is to fold documentation visuals into the process you already have.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most teams already have automation for testing, linting, and deployment. Screenshots-as-code adds one more step to that automation. It uses the same infrastructure, the same review process, and the same deployment cadence. No new tools to learn, no new workflows to adopt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freshness Boundaries
&lt;/h2&gt;

&lt;p&gt;One of the most powerful aspects of the automation approach is that you get &lt;strong&gt;bounded freshness&lt;/strong&gt;. If your automation runs on every merge to main, your screenshots are never more than one merge behind the current localhost build you captured. If it runs weekly, your maximum drift is seven days.&lt;/p&gt;

&lt;p&gt;Compare this to manual processes where screenshots can drift for months or years without anyone noticing.&lt;/p&gt;

&lt;p&gt;You can enforce freshness at different levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard gate&lt;/strong&gt;: Fail the build if any screenshot diffs exceed a threshold. This is aggressive but guarantees zero visual debt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft gate&lt;/strong&gt;: Create a PR but do not block the build. This is more practical for most teams and keeps visual updates in the review queue without slowing down feature development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring only&lt;/strong&gt;: Capture diffs and report them to a dashboard without any blocking. Useful as a first step when introducing screenshots-as-code to a team.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where This Approach Came From
&lt;/h2&gt;

&lt;p&gt;The idea of treating screenshots as code did not emerge in a vacuum. It follows a well-established pattern in software engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2013-2015&lt;/strong&gt;: Infrastructure as code (Terraform, CloudFormation) replaces manual server configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2016-2018&lt;/strong&gt;: Configuration as code (Kubernetes manifests, Docker Compose) replaces manual deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2018-2020&lt;/strong&gt;: Policy as code (OPA, Sentinel) replaces manual compliance checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2020-2023&lt;/strong&gt;: Visual testing as code (Percy, Chromatic) replaces manual visual QA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2024-present&lt;/strong&gt;: Screenshots as code replaces manual documentation visuals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each wave applies the same core insight: &lt;strong&gt;anything that can be defined declaratively and executed automatically should be.&lt;/strong&gt; Manual processes do not scale, introduce human error, and lack auditability.&lt;/p&gt;

&lt;p&gt;For a deeper look at the costs of the manual approach, see &lt;a href="https://dev.to/visual-debt"&gt;what visual debt actually costs your team&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Objections
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "Our screenshots need custom annotations and callouts."
&lt;/h3&gt;

&lt;p&gt;Annotations can be config-driven too. Define bounding boxes, arrow positions, and label text in the capture configuration. The automation applies them consistently every time. This actually produces &lt;em&gt;better&lt;/em&gt; annotations than manual work because placement is pixel-precise and consistent across every screenshot.&lt;/p&gt;

&lt;h3&gt;
  
  
  "We need test data, not production data."
&lt;/h3&gt;

&lt;p&gt;Prefer a localhost build with deterministic seed data. The point is to run the shipped app locally inside CI, not to capture against production URLs. This is the same approach you already use for reliable end-to-end tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Our docs team does not know automation."
&lt;/h3&gt;

&lt;p&gt;They do not need to. The config file is JSON -- any technical writer can edit it. The capture runs as a CLI command, and the PR review process is the same one they already use for content changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  "What about edge cases where automation cannot reach the right state?"
&lt;/h3&gt;

&lt;p&gt;Every approach has edge cases. The goal is to automate 80-90% of your screenshots and handle the remaining 10-20% through a documented manual process. Even partial automation dramatically reduces visual debt accumulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;You do not need to convert your entire documentation set overnight. Start with a pilot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick 10-20 high-traffic screenshots.&lt;/strong&gt; Choose the ones that appear in your getting-started guide or most-visited docs pages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the scenario config.&lt;/strong&gt; Define the URLs, selectors, and steps needed to reproduce each screenshot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it locally.&lt;/strong&gt; Verify that the automated captures match your current screenshots closely enough.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add it to CI.&lt;/strong&gt; Start with monitoring-only mode. Let it run for two weeks and review the diff reports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expand gradually.&lt;/strong&gt; Add more captures to the config as you gain confidence in the approach.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://dev.to/docs"&gt;Reshot docs&lt;/a&gt; walk through this workflow end-to-end: config-driven capture, variant management, visual diffing, and local automation.&lt;/p&gt;

&lt;p&gt;The capture half of this is the part you &lt;em&gt;can&lt;/em&gt; build yourself -- Playwright plus a shell script will take a screenshot. What that script will not give you is the hard half: a &lt;strong&gt;review queue&lt;/strong&gt; so a non-engineer can approve a changed screenshot, a &lt;strong&gt;visual registry&lt;/strong&gt; that tracks the approved state of every variant, and &lt;strong&gt;durable URLs&lt;/strong&gt; that flip to the new image on approval so every doc, README, and embed updates at once without a find-and-replace. Owning that layer -- diff storage, approval state, CDN invalidation, dead-link safety across hundreds of embeds -- is a product, not a script. That is the part Reshot manages so your team does not have to. The principles in this post are tool-agnostic; the managed review-and-delivery layer is where the actual leverage lives.&lt;/p&gt;

</description>
      <category>screenshots</category>
      <category>automation</category>
      <category>documentation</category>
      <category>ci</category>
    </item>
  </channel>
</rss>
