<?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: kate astrid</title>
    <description>The latest articles on DEV Community by kate astrid (@kate_astrid).</description>
    <link>https://dev.to/kate_astrid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1154209%2F48fdfa0a-d2bf-4410-bb49-6fcec9e039da.jpeg</url>
      <title>DEV Community: kate astrid</title>
      <link>https://dev.to/kate_astrid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kate_astrid"/>
    <language>en</language>
    <item>
      <title>The Wrong Question: Why Blameless Culture Builds Better Engineering Teams</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Wed, 20 May 2026 10:15:58 +0000</pubDate>
      <link>https://dev.to/kate_astrid/the-wrong-question-why-blameless-culture-builds-better-engineering-teams-38di</link>
      <guid>https://dev.to/kate_astrid/the-wrong-question-why-blameless-culture-builds-better-engineering-teams-38di</guid>
      <description>&lt;p&gt;&lt;em&gt;The case for blameless culture — not as a feel-good HR initiative, but as a practical operating principle for teams building systems too complex for any one person to fully understand.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It’s 2:47am.&lt;/p&gt;

&lt;p&gt;Production is down.&lt;/p&gt;

&lt;p&gt;A deploy went out an hour ago, the on-call engineer is staring at a graph that fell off a cliff, and somewhere in a Slack channel someone is typing the question that quietly poisons engineering organizations:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who pushed this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It feels like a reasonable question.&lt;/p&gt;

&lt;p&gt;Something broke. Someone did it. Find them, talk to them, make sure it doesn’t happen again. Clean, logical, satisfying.&lt;/p&gt;

&lt;p&gt;It’s also almost always the wrong question.&lt;/p&gt;

&lt;p&gt;The teams that figure this out tend to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship faster,&lt;/li&gt;
&lt;li&gt;recover quicker,&lt;/li&gt;
&lt;li&gt;learn more effectively,&lt;/li&gt;
&lt;li&gt;and retain their best engineers longer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The teams that don’t keep repeating the same classes of incidents while wondering why “people keep making mistakes.”&lt;/p&gt;




&lt;h2&gt;
  
  
  What Blameless Culture Actually Means
&lt;/h2&gt;

&lt;p&gt;There’s a common misconception that “blameless” means “no accountability.”&lt;/p&gt;

&lt;p&gt;It doesn’t.&lt;/p&gt;

&lt;p&gt;Blameless culture isn’t the absence of responsibility — it’s the redirection of attention.&lt;/p&gt;

&lt;p&gt;A blameless team still cares deeply about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding what went wrong,&lt;/li&gt;
&lt;li&gt;identifying who was involved and what decisions they made,&lt;/li&gt;
&lt;li&gt;holding people responsible for follow-through on fixes,&lt;/li&gt;
&lt;li&gt;and maintaining a high quality bar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it stops treating as useful is punishment as the primary mechanism for improvement.&lt;/p&gt;

&lt;p&gt;The premise is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a sufficiently complex system, the question “who caused this?” almost always resolves to “a competent engineer doing reasonable work in a context that made the bad outcome easier than the good one.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The outage, the data loss, the bad deploy — these are usually the visible tip of a much larger iceberg:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing guardrails,&lt;/li&gt;
&lt;li&gt;unclear ownership,&lt;/li&gt;
&lt;li&gt;undocumented assumptions,&lt;/li&gt;
&lt;li&gt;brittle tooling,&lt;/li&gt;
&lt;li&gt;or processes that quietly invite mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Punish the engineer at the tip, and the iceberg remains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Blame Feels Right (And Why That’s a Trap)
&lt;/h2&gt;

&lt;p&gt;Blame is psychologically satisfying for the same reason scapegoating has existed in every human culture for thousands of years:&lt;/p&gt;

&lt;p&gt;it provides closure.&lt;/p&gt;

&lt;p&gt;Something bad happened.&lt;/p&gt;

&lt;p&gt;We identified the cause.&lt;/p&gt;

&lt;p&gt;We addressed it.&lt;/p&gt;

&lt;p&gt;We move on.&lt;/p&gt;

&lt;p&gt;The problem is that blame as an organizational instinct has predictable failure modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It Teaches People to Hide Things
&lt;/h3&gt;

&lt;p&gt;The fastest way to ensure you never hear about the near-miss is to punish people for the misses.&lt;/p&gt;

&lt;p&gt;Engineers learn quickly.&lt;/p&gt;

&lt;p&gt;If reporting mistakes costs them socially or professionally, they’ll route around the reporting.&lt;/p&gt;

&lt;p&gt;Incidents become sanitized.&lt;/p&gt;

&lt;p&gt;Near-misses disappear.&lt;/p&gt;

&lt;p&gt;The organization slowly loses its ability to see itself clearly.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. It Optimizes for the Wrong Outcome
&lt;/h3&gt;

&lt;p&gt;Blame focuses on the last person who touched the system.&lt;/p&gt;

&lt;p&gt;But the last person to touch the system is rarely where the leverage is.&lt;/p&gt;

&lt;p&gt;The leverage is usually in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the deploy process that allowed the change,&lt;/li&gt;
&lt;li&gt;the test suite that didn’t catch it,&lt;/li&gt;
&lt;li&gt;the review process that waved it through,&lt;/li&gt;
&lt;li&gt;or the documentation that failed to explain a constraint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Punishing the engineer doesn’t improve any of those.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. It Selects Against the People You Most Want to Keep
&lt;/h3&gt;

&lt;p&gt;Senior engineers who can work anywhere tend to leave organizations where incidents become career events.&lt;/p&gt;

&lt;p&gt;The engineers who stay often become more risk-averse.&lt;/p&gt;

&lt;p&gt;Which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer ambitious changes,&lt;/li&gt;
&lt;li&gt;slower iteration,&lt;/li&gt;
&lt;li&gt;more defensive engineering,&lt;/li&gt;
&lt;li&gt;and less ownership of ambiguous work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You end up with a team optimized for self-preservation rather than impact.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. It Misunderstands How Complex Systems Fail
&lt;/h3&gt;

&lt;p&gt;Human factors researchers like Sidney Dekker and James Reason have spent decades documenting this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In complex systems, failures are almost never caused by a single bad actor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They emerge from the unlucky alignment of many individually reasonable decisions.&lt;/p&gt;

&lt;p&gt;Blame assumes a simple causal chain. Real incidents almost never have one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changes When Blame Leaves the Room
&lt;/h2&gt;

&lt;p&gt;Teams that genuinely commit to blamelessness — not just as a slogan, but as a discipline — tend to see a set of compounding changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postmortems Become Useful
&lt;/h3&gt;

&lt;p&gt;When the document isn’t a trial, it can be honest.&lt;/p&gt;

&lt;p&gt;Engineers describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what they actually thought,&lt;/li&gt;
&lt;li&gt;what they actually tried,&lt;/li&gt;
&lt;li&gt;what they misunderstood,&lt;/li&gt;
&lt;li&gt;and where their mental model diverged from reality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the raw material organizations need to improve.&lt;/p&gt;

&lt;p&gt;A defensive postmortem teaches you nothing. An honest one teaches you everything.&lt;/p&gt;




&lt;h3&gt;
  
  
  Near-Misses Get Reported
&lt;/h3&gt;

&lt;p&gt;This is one of the highest-leverage effects.&lt;/p&gt;

&lt;p&gt;For every outage that happens, there are usually many more that &lt;em&gt;almost&lt;/em&gt; happened.&lt;/p&gt;

&lt;p&gt;Caught at the last second. Or mitigated in staging. Or rolled back before users noticed.&lt;/p&gt;

&lt;p&gt;In a blame culture, those incidents disappear. In a blameless culture, they become cheap learning opportunities.&lt;/p&gt;




&lt;h3&gt;
  
  
  Incident Response Gets Faster
&lt;/h3&gt;

&lt;p&gt;When engineers aren’t worried about implicating themselves, they share information faster.&lt;/p&gt;

&lt;p&gt;The unspoken fear of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Will this make me look bad?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;slows down active incident response more than most organizations realize.&lt;/p&gt;

&lt;p&gt;Removing that fear speeds teams up.&lt;/p&gt;




&lt;h3&gt;
  
  
  Junior Engineers Ramp Faster
&lt;/h3&gt;

&lt;p&gt;The cost of being wrong strongly determines how quickly junior engineers grow.&lt;/p&gt;

&lt;p&gt;In blame-heavy environments, juniors learn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defer,&lt;/li&gt;
&lt;li&gt;ask permission constantly,&lt;/li&gt;
&lt;li&gt;avoid ownership,&lt;/li&gt;
&lt;li&gt;and stay inside safe boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In blameless environments, they learn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take swings,&lt;/li&gt;
&lt;li&gt;recover,&lt;/li&gt;
&lt;li&gt;communicate clearly,&lt;/li&gt;
&lt;li&gt;and build judgment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Systemic Fixes Actually Get Built
&lt;/h3&gt;

&lt;p&gt;When conversations move from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Be more careful.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What would make this class of mistake impossible?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;teams start investing in compounding improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better tests,&lt;/li&gt;
&lt;li&gt;safer deploy tooling,&lt;/li&gt;
&lt;li&gt;stronger defaults,&lt;/li&gt;
&lt;li&gt;clearer ownership,&lt;/li&gt;
&lt;li&gt;and more visible operational signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each fix removes an entire category of future incidents.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Blameless Culture Is &lt;em&gt;Not&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Blamelessness gets misused often enough that it’s worth being explicit about the boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s Not Consequence-Free
&lt;/h3&gt;

&lt;p&gt;If an engineer repeatedly ignores process, cuts corners, or refuses feedback, that’s a performance issue.&lt;/p&gt;

&lt;p&gt;Blameless culture doesn’t mean ignoring patterns.&lt;/p&gt;

&lt;p&gt;It means not treating a single incident as evidence of bad character.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not the Absence of Standards
&lt;/h3&gt;

&lt;p&gt;Some of the most rigorous engineering organizations in the world are also deeply blameless.&lt;/p&gt;

&lt;p&gt;The quality bar is still high.&lt;/p&gt;

&lt;p&gt;The difference is that standards are enforced through systems, not shame.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not “Everything Is the Company’s Fault”
&lt;/h3&gt;

&lt;p&gt;There’s a lazy version of blamelessness that gestures vaguely at “the system” while removing all individual responsibility.&lt;/p&gt;

&lt;p&gt;That’s not useful either.&lt;/p&gt;

&lt;p&gt;Engineers still own their work.&lt;/p&gt;

&lt;p&gt;The shift is in what happens &lt;em&gt;after&lt;/em&gt; mistakes occur.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not Infinitely Patient With Repeated Mistakes
&lt;/h3&gt;

&lt;p&gt;If the same person repeatedly makes the same category of mistake after systemic fixes are already in place, that’s a signal.&lt;/p&gt;

&lt;p&gt;Blamelessness is a default posture for learning.&lt;/p&gt;

&lt;p&gt;Not permanent immunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  How To Actually Do It
&lt;/h2&gt;

&lt;p&gt;Cultural change is mostly invisible work.&lt;/p&gt;

&lt;p&gt;But there are a few practices that consistently distinguish teams that live this from teams that merely say it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Postmortems in the Second-Person Plural
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Alex deployed a bad change.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We deployed a change that…”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The information content is the same, but the framing changes everything: one treats an individual as the defendant, the other treats the team as the system.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ask “How?” Before “Who?”
&lt;/h3&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who reviewed this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;vs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How did this make it through review?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first surfaces a person, the second surfaces a process gap.&lt;/p&gt;

&lt;p&gt;One produces defensiveness, the other produces better systems.&lt;/p&gt;




&lt;h3&gt;
  
  
  Separate Incident Reviews From Performance Reviews
&lt;/h3&gt;

&lt;p&gt;These should never happen in the same room, with the same incentives, on the same timeline.&lt;/p&gt;

&lt;p&gt;The moment an incident review starts feeling like a performance evaluation, honesty collapses.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reward Engineers Who Surface Their Own Mistakes
&lt;/h3&gt;

&lt;p&gt;This is one of the strongest cultural signals leadership can send.&lt;/p&gt;

&lt;p&gt;The engineer who says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I deployed something I shouldn’t have. Here’s what I learned.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;should be visibly appreciated for transparency.&lt;/p&gt;

&lt;p&gt;The behavior organizations reward becomes the behavior they get.&lt;/p&gt;




&lt;h3&gt;
  
  
  Fix the System, Every Time
&lt;/h3&gt;

&lt;p&gt;Every postmortem should produce at least one concrete change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a test,&lt;/li&gt;
&lt;li&gt;a guardrail,&lt;/li&gt;
&lt;li&gt;a tooling improvement,&lt;/li&gt;
&lt;li&gt;a documentation fix,&lt;/li&gt;
&lt;li&gt;or a process adjustment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the only takeaway is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Be more careful.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then nothing meaningful was learned.&lt;/p&gt;

&lt;p&gt;“Be more careful” is the null result of incident analysis.&lt;/p&gt;




&lt;h3&gt;
  
  
  Make Leaders Go First
&lt;/h3&gt;

&lt;p&gt;Culture is set by what senior people do, not what they say.&lt;/p&gt;

&lt;p&gt;The first time a VP, tech lead, or founder publicly owns a mistake and frames it as a learning opportunity, the organization recalibrates.&lt;/p&gt;

&lt;p&gt;The first time they punish someone for an honest mistake, it recalibrates in the opposite direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deeper Point
&lt;/h2&gt;

&lt;p&gt;There’s a version of this argument that’s purely pragmatic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blameless cultures recover faster,&lt;/li&gt;
&lt;li&gt;incidents become learning opportunities,&lt;/li&gt;
&lt;li&gt;teams ship more confidently,&lt;/li&gt;
&lt;li&gt;and organizations retain stronger engineers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that is true.&lt;/p&gt;

&lt;p&gt;But the deeper point is this:&lt;/p&gt;

&lt;p&gt;Modern software systems are too complex for any one person to fully understand.&lt;/p&gt;

&lt;p&gt;Teams of dozens or hundreds of engineers build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;distributed systems,&lt;/li&gt;
&lt;li&gt;continuously deployed infrastructure,&lt;/li&gt;
&lt;li&gt;services with millions of users,&lt;/li&gt;
&lt;li&gt;and architectures evolving faster than any individual can mentally model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that environment, the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who is responsible for this failure?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is usually less useful than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What about our system made this failure possible, and what would make it impossible next time?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Blame is a tool from a simpler world. One where individuals could meaningfully own the totality of an outcome.&lt;/p&gt;

&lt;p&gt;That world no longer describes modern software engineering.&lt;/p&gt;

&lt;p&gt;The teams that understand this — the ones that shift from punishment to learning, from defense to curiosity, from individuals to systems — are the ones that will build the best software over the next decade.&lt;/p&gt;

&lt;p&gt;The rest will keep asking who pushed the bad commit. And they’ll keep wondering why the same kinds of incidents keep happening.&lt;/p&gt;

&lt;p&gt;The best teams don't have fewer mistakes. They have better conversations about them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If this topic resonated, these are worth reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;The Field Guide to Understanding “Human Error”&lt;/em&gt; — Sidney Dekker&lt;/li&gt;
&lt;li&gt;John Allspaw’s writing on incident analysis&lt;/li&gt;
&lt;li&gt;Google’s SRE Workbook chapters on postmortem culture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them are specifically about software, but all of them are deeply relevant to software.&lt;/p&gt;

</description>
      <category>blameless</category>
      <category>culture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How We Halved Our Playwright E2E Suite</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 19 May 2026 09:37:01 +0000</pubDate>
      <link>https://dev.to/kate_astrid/how-we-halved-our-playwright-e2e-suite-mg6</link>
      <guid>https://dev.to/kate_astrid/how-we-halved-our-playwright-e2e-suite-mg6</guid>
      <description>&lt;p&gt;&lt;em&gt;Five patterns that took a slow, flaky Playwright suite down to fast and stable.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We had a Playwright E2E suite that had grown into a painful, flaky part of CI.&lt;/p&gt;

&lt;p&gt;Dozens of login/logout cycles per file. Fixed &lt;code&gt;waitForTimeout&lt;/code&gt; calls everywhere. Hundreds of UI clicks just to &lt;em&gt;set up&lt;/em&gt; state before each assertion.&lt;/p&gt;

&lt;p&gt;Every other run, something would fail. We'd retry, hope for green, and move on.&lt;/p&gt;

&lt;p&gt;Eventually we paid the tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Before / After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime (clean)&lt;/td&gt;
&lt;td&gt;~37 min&lt;/td&gt;
&lt;td&gt;~17 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI reliability&lt;/td&gt;
&lt;td&gt;~50% flaky runs&lt;/td&gt;
&lt;td&gt;Stable with normal retries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across the broader suite, a similar pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dropped about a dozen low-value tests,&lt;/li&gt;
&lt;li&gt;replaced one E2E test with a focused unit suite that runs in 1.2 seconds instead of 30,&lt;/li&gt;
&lt;li&gt;and stripped fixed waits from the most-trafficked files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five patterns did most of the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 1: Programmatic Auth
&lt;/h2&gt;

&lt;p&gt;Login through a form fill takes 2–3 seconds on a good day.&lt;/p&gt;

&lt;p&gt;With 40+ login/logout cycles in a file, that's two minutes of click ceremony before you've asserted anything useful.&lt;/p&gt;

&lt;p&gt;Most modern auth setups support a programmatic path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in via the auth provider's REST endpoint.&lt;/li&gt;
&lt;li&gt;Write the resulting token to &lt;code&gt;localStorage&lt;/code&gt; (or wherever your client SDK reads from).&lt;/li&gt;
&lt;li&gt;Skip the form entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mirror it for logout — clear the session storage and cookies, then navigate to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last navigation is the part I'd skip on first attempt and regret.&lt;/p&gt;

&lt;p&gt;Without it, after clearing storage the page is still on &lt;code&gt;/dashboard/something&lt;/code&gt; and the SPA detects "no session" and starts its own redirect to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next &lt;code&gt;loginAs&lt;/code&gt; then races that redirect, and you get sporadic &lt;code&gt;"navbar not visible"&lt;/code&gt; errors that look like flakes but are actually deterministic timing bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 2: API-Driven Setup
&lt;/h2&gt;

&lt;p&gt;This was the single biggest win for us.&lt;/p&gt;

&lt;p&gt;Most of what tests do to &lt;em&gt;set up&lt;/em&gt; state is already callable via REST — those endpoints exist because the app uses them.&lt;/p&gt;

&lt;p&gt;There's no reason to drive the UI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a user to a group,&lt;/li&gt;
&lt;li&gt;configure a setting,&lt;/li&gt;
&lt;li&gt;assign a role,&lt;/li&gt;
&lt;li&gt;or seed fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;when you could just &lt;code&gt;POST&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Before: six UI steps for setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/groups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Test Group/&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add Member&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TEST_USER_EMAIL&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ... wait for it to persist, navigate back, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After: one API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureUserInGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAuthHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/api/groups/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GROUP_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/members/add`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER_ID&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test setup went from ~25–30 seconds to ~3 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tradeoff
&lt;/h3&gt;

&lt;p&gt;API-based setup means a test can pass even if the UI setup flow itself is broken.&lt;/p&gt;

&lt;p&gt;We accepted that tradeoff.&lt;/p&gt;

&lt;p&gt;We kept dedicated tests for the setup flows themselves, and the rest of the suite became free to focus on the behavior it actually existed to verify.&lt;/p&gt;

&lt;p&gt;The principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exercise the UI for what you're actually testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A test asserting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The frontend hides button X for a viewer”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;does not need to configure viewer permissions through the UI too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 3: State Assertions, Not Fixed Waits
&lt;/h2&gt;

&lt;p&gt;Hardcoded &lt;code&gt;page.waitForTimeout(1500)&lt;/code&gt; calls were everywhere — usually under comments like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wait for it to commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a fast CI day, harmless.&lt;/p&gt;

&lt;p&gt;On a slow CI day, flaky.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;assert on the actual signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="alert"][aria-label="Saved"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faster on the happy path,&lt;/li&gt;
&lt;li&gt;more reliable under load,&lt;/li&gt;
&lt;li&gt;and easier to reason about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reach for &lt;code&gt;expect.toPass()&lt;/code&gt; with a polled async block when the signal comes from another page or from eventual backend consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 4: For “Was This Persisted?” — Ask the API, Not the DOM
&lt;/h2&gt;

&lt;p&gt;This was the subtlest bug in the suite, and the one we learned the hard way after a CI regression we couldn't reproduce locally.&lt;/p&gt;

&lt;p&gt;If your frontend uses an optimistic-update library like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RTK Query&lt;/li&gt;
&lt;li&gt;React Query&lt;/li&gt;
&lt;li&gt;SWR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the DOM can temporarily lie about persistence.&lt;/p&gt;

&lt;p&gt;A row hidden by an optimistic delete looks identical to a row that was actually deleted — until the cache refetches in the background and the row pops back into existence.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;toHaveCount(0)&lt;/code&gt; assertion succeeds against the optimistic state, then the &lt;em&gt;next&lt;/em&gt; assertion in the test finds the row again.&lt;/p&gt;

&lt;p&gt;When you're asserting that &lt;em&gt;persisted&lt;/em&gt; state changed, poll the backend until the change is actually committed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/.../items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea for permission revocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/.../resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DOM answers:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“What is rendered right now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The API answers:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Did this actually commit?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are not always the same question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 5: Audit for Redundancy
&lt;/h2&gt;

&lt;p&gt;After the speedups, the suite was still larger than it needed to be.&lt;/p&gt;

&lt;p&gt;On honest review, three categories of redundancy stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symmetric Mirror Tests
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“User has elevated role + base permission”&lt;/li&gt;
&lt;li&gt;“User has base role + elevated permission”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we see here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different setup states. &lt;/li&gt;
&lt;li&gt;Identical observable assertions. &lt;/li&gt;
&lt;li&gt;Same backend path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One test covered the public contract sufficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup Tests Duplicating Hooks
&lt;/h3&gt;

&lt;p&gt;We had tests whose sole purpose was deleting leftover fixtures.&lt;/p&gt;

&lt;p&gt;But the suite already had &lt;code&gt;afterAll&lt;/code&gt; cleanup hooks with retry safety.&lt;/p&gt;

&lt;p&gt;The cleanup “tests” added runtime, not coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Component Smoke Tests
&lt;/h3&gt;

&lt;p&gt;Several tests asserted things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the list loads,&lt;/li&gt;
&lt;li&gt;pagination works,&lt;/li&gt;
&lt;li&gt;columns render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But those shared components were already exercised by nearly every feature test in the suite.&lt;/p&gt;

&lt;p&gt;The smoke tests weren't paying for themselves.&lt;/p&gt;

&lt;p&gt;The audit removed roughly a third of tests with no meaningful coverage loss.&lt;/p&gt;

&lt;p&gt;A useful question turned out to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is this test distinct?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not merely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Does this test pass?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What We &lt;em&gt;Didn't&lt;/em&gt; Do
&lt;/h2&gt;

&lt;p&gt;Some dead ends are worth documenting too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelizing Tests That Shared Accounts
&lt;/h3&gt;

&lt;p&gt;Concurrent logins for the same backend user triggered session revocation cascades across contexts.&lt;/p&gt;

&lt;p&gt;We reverted it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusing Auth Sessions Across Browser Contexts
&lt;/h3&gt;

&lt;p&gt;Several client SDKs rejected rehydrated sessions across multiple contexts.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;loginAs()&lt;/code&gt; now performs a fresh REST sign-in.&lt;/p&gt;

&lt;p&gt;The overhead (~200ms) was small enough that determinism mattered more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Short Retry Budgets for Permission Assertions
&lt;/h3&gt;

&lt;p&gt;Eventually consistent systems can take longer than expected, especially during deploys.&lt;/p&gt;

&lt;p&gt;A 15-second retry window was not enough.&lt;/p&gt;

&lt;p&gt;We replaced short retries with explicit API polling patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you're using the UI as a setup harness, you're paying twice.&lt;/strong&gt;&lt;br&gt;
Use APIs to create state and reserve the UI for the behavior you actually care about.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replace &lt;code&gt;waitForTimeout()&lt;/code&gt; with state assertions wherever possible.&lt;/strong&gt;&lt;br&gt;
Fixed waits are bets against your worst CI day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For “was this persisted?” — ask the API, not the DOM.&lt;/strong&gt;&lt;br&gt;
The DOM can be optimistic, cached, or mid-render.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Programmatic auth compounds across every test.&lt;/strong&gt;&lt;br&gt;
The savings stack up quickly, and you eliminate subtle redirect races.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit your suite for true redundancy.&lt;/strong&gt;&lt;br&gt;
“All passing” is not the same as “all paying for themselves.”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;There’s no silver bullet here — just a stack of small, principled changes that compound.&lt;/p&gt;

&lt;p&gt;Fast E2E suites usually aren't the result of one big optimization.&lt;/p&gt;

&lt;p&gt;They're the result of removing dozens of tiny sources of unnecessary work.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



</description>
      <category>e2e</category>
      <category>playwright</category>
    </item>
    <item>
      <title>From Chaos to Clarity: Fixing Our Monorepo</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 19 Aug 2025 11:45:37 +0000</pubDate>
      <link>https://dev.to/epilot/from-chaos-to-clarity-fixing-our-monorepo-2bih</link>
      <guid>https://dev.to/epilot/from-chaos-to-clarity-fixing-our-monorepo-2bih</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction: Why This Story Might Save You Some Headaches&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you’ve worked in a large codebase, you’ve probably seen this: the code runs, but every change takes far longer than it should. Updating one feature often breaks another. Adding a dependency means worrying about conflicts. Old build tools still linger, and no one feels safe upgrading packages that half the system depends on.&lt;/p&gt;

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

&lt;p&gt;That’s exactly what we faced in our &lt;code&gt;journey-monorepo&lt;/code&gt;, the main repository for our team. Dependencies were wired together in inconsistent ways, different teams had introduced multiple overlapping build tools, and some libraries hadn’t been touched in years. Proposing a cleanup felt like asking to redesign a city while people were still living in it — risky, disruptive, and easy to delay indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;So why did we bother?&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Development had slowed to a crawl.&lt;/em&gt; Builds were taking too long, and waiting minutes just to see a change in the browser was killing productivity.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Every change felt risky.&lt;/em&gt; The dependency spaghetti meant a small tweak in one package could break multiple apps — often in ways we wouldn’t notice until much later.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Onboarding new developers was painful.&lt;/em&gt; It took too long for new team members to get up to speed, mostly because they had to learn multiple bundlers, testing setups, and outdated tooling quirks.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Technical debt was blocking new features.&lt;/em&gt; We were spending more time patching, debugging, and wrestling with the repo than building the actual features our users cared about.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Costs were adding up.&lt;/em&gt; Longer build times meant higher CI costs, and outdated packages made security updates and maintenance more expensive in the long run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, the longer we waited, the more time, money, and momentum we were losing.&lt;/p&gt;

&lt;p&gt;And, hey, now we get a good story out of it.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;Journey-Monorepo in a Nutshell&lt;/strong&gt;
&lt;/h2&gt;

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

&lt;p&gt;&lt;em&gt;Journey-monorepo&lt;/em&gt; is the central place on the epilot platform for everything related to journeys. What you see in the screenshot above represents two main apps: &lt;em&gt;journey-app&lt;/em&gt; on the left side and &lt;em&gt;journey-builder&lt;/em&gt; on the right. &lt;em&gt;Journey-builder&lt;/em&gt; is responsible for configuring journeys, and &lt;em&gt;journey-app&lt;/em&gt; for rendering them. &lt;br&gt;
Like in many startups, the repo started out clean and well-structured, but as the product grew quickly, the codebase expanded in unplanned ways — things were added wherever they seemed to fit at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The “BEFORE” Setup&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apps:
- journey-builder.               
- journey-app
- entity-mapping
- journey-view

Packages:
- blocks-configurators
- blocks-renderers
- journey-elements
- journey-utils
- journey-logics-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me show you the rough map of dependencies we had step by step. We are interested in two main apps: &lt;code&gt;journey-builder&lt;/code&gt; and &lt;code&gt;journey-app&lt;/code&gt;, so I am going to dive deeper into their connections. It is not important which app or package does what. What IS important is to see how many connections we had to keep in mind every time we needed to change anything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ok, this is fine.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf5p4t1zdjxf169gq22z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf5p4t1zdjxf169gq22z.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This is too.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8udxll5y9ewrp6q8nboq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8udxll5y9ewrp6q8nboq.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yeah, cool.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy2gcleacdlwo85nozw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy2gcleacdlwo85nozw0.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Packages already become tangled, let's keep going.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncrjunqvi3x0zojsp82d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncrjunqvi3x0zojsp82d.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ok, even more connections.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqtr5pcsd766pxyhld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqtr5pcsd766pxyhld.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Well, this is just a delinquent madness.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptjuqypr9w8yp04lzbmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptjuqypr9w8yp04lzbmj.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might say: Kate, come on, this is what happens in every big project - things become deeply intertwined, and it is a normal thing to experience. &lt;br&gt;
I will answer: yes, but I haven't finished.&lt;/p&gt;

&lt;p&gt;Let's now add the build tools to this table to have the full picture.&lt;/p&gt;

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

&lt;p&gt;Well, what we have here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webpack;&lt;/li&gt;
&lt;li&gt;Craco;&lt;/li&gt;
&lt;li&gt;Tsup;&lt;/li&gt;
&lt;li&gt;Tsx;&lt;/li&gt;
&lt;li&gt;Tsdx.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't know about you, but I hadn't worked with half of them before I started working on this repo. &lt;/p&gt;

&lt;p&gt;In the end, it was a web of “if you change this, you might break that” — and “that” could be anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How We Tackled It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We kicked things off with an RFC — basically, “let’s write this down so we don’t lose the plot halfway through.”&lt;br&gt;
Our two big goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unify the tooling so we weren’t juggling five different build setups.&lt;/li&gt;
&lt;li&gt;Untangle dependencies so teams could work without stepping on each other’s toes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is exactly what we did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Cut the Cord on Unnecessary Dependencies&lt;/strong&gt;&lt;br&gt;
The first thing we had to do was face the dependency jungle. Over time, different teams had introduced overlapping packages, and no one was quite sure what depended on what anymore. Cleaning this up gave us a chance to regain control.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Untangled all the apps and packages as much as possible.&lt;/em&gt; We mapped out how everything connected and then started cutting back the unnecessary links. This meant reducing cross-dependencies that caused fragile builds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Moved blocks-configurators into journey-builder.&lt;/em&gt; These configurators were never meant to be used outside, so pulling them closer to where they belong simplified the dependency graph.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Moved blocks-renderers into journey-app.&lt;/em&gt; Renderers were core to the application itself, so consolidating them in the main app made the architecture more intuitive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Deleted journey-utils and journey-elements.&lt;/em&gt; &lt;em&gt;Journey-utils&lt;/em&gt; was a very small package with some utilities that were moved to other packages. And &lt;em&gt;journey-elements _ were later replaced by _concorde-elements&lt;/em&gt;. You can read more about it in &lt;a href="https://dev.to/epilot/building-a-scalable-react-component-library-lessons-from-concorde-elements-kdi"&gt;this article written by my partner in crime.&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a painful step, but once the dust settled, the repo felt lighter and easier to reason about. We no longer had to worry about accidentally breaking other parts of the system when making small changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Standardize the Tooling&lt;/strong&gt;&lt;br&gt;
After trimming the dependencies, the next big challenge was our tooling. Different parts of the repo used different build systems, which slowed everyone down and made onboarding new developers a headache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Phased out Craco, Tsup, and Tsdx by switching to Vite.&lt;/em&gt; These older tools had been added over time, each solving a narrow need. Vite gave us a unified, modern setup with faster builds and simpler configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Swapped Jest for Vitest&lt;/em&gt; — faster tests, modern setup. Jest had served us well, but it was slow and required custom patching to keep working. Vitest gave us near-instant feedback and worked seamlessly with Vite, making the dev experience far smoother.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By unifying the tooling, we not only sped up day-to-day work but also reduced cognitive load. Developers no longer had to remember which tool applied to which package — everything just worked the same way everywhere.&lt;/p&gt;

&lt;p&gt;This is how the "AFTER" setup looks:&lt;/p&gt;

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

&lt;p&gt;As you can see, the monorepo is now far more streamlined — there are significantly fewer dependencies crisscrossing between packages, and we’ve eliminated most of the variations in build tools. This makes the structure easier to understand, faster to work with, and much less fragile when changes are introduced. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;After the Restructuring: What We Learned&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When the dust settled, it wasn’t just that the dependency graph was cleaner — we were thinking about the repo differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Immediate Wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Changing things stopped feeling dangerous.&lt;/em&gt; Before, touching a shared package felt like pulling a brick from a Jenga tower. Now, fewer connections mean less risk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Builds and tests stopped feeling like a punishment.&lt;/em&gt; Vite’s dev server = instant feedback. Vitest shaved minutes off test runs. Devs started running tests more often just because they could.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Onboarding went from “ugh” to “okay.”&lt;/em&gt; New folks could get set up without learning the quirks of four bundlers first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Slower Payoffs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Tooling swaps aren’t magic.&lt;/em&gt; Some Webpack plugins didn’t have a Vite equivalent, so we had to rethink features or drop them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Vitest exposed bad tests.&lt;/em&gt; Some “passing” tests were only passing thanks to Jest’s quirks. Painful, but worth fixing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-Offs &amp;amp; Reality Checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Not everything got unified.&lt;/em&gt; A couple of apps still run on legacy setups because migrating them wasn’t worth the risk — for now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Shared packages need rules.&lt;/em&gt; We now ask “does this really need to be shared?” before creating new ones.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Refactoring is never “done.”&lt;/em&gt; There is always room for improvement, we all know. that. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tips If You’re Thinking About Doing This&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw your dependency graph before you start — it’ll help convince others (and yourself) that the cleanup’s worth it.&lt;/li&gt;
&lt;li&gt;Expect setbacks. Some PRs will break things. That’s normal.&lt;/li&gt;
&lt;li&gt;Automate checks for unused or outdated packages — saves a ton of review time.&lt;/li&gt;
&lt;li&gt;Celebrate small wins. Removing the last Webpack config deserved cake.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This wasn’t a quick tidy-up — it was a half-year-long deliberate overhaul alongside working on other features. But it’s made a massive difference to how we work: faster builds, fewer “don’t touch that” areas, and a general feeling that the repo is ours again, not a beast we’re afraid of.&lt;/p&gt;

&lt;p&gt;If I had to wrap it all up in one bit of advice?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Keep it simple.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every extra dependency, tool, or package you skip today is one less thing you’ll have to untangle in the future.&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>monorepo</category>
      <category>epilot</category>
      <category>frontend</category>
    </item>
    <item>
      <title>What To Expect When You Join Epilot</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Mon, 03 Feb 2025 13:24:46 +0000</pubDate>
      <link>https://dev.to/epilot/what-to-expect-when-you-join-epilot-1hc1</link>
      <guid>https://dev.to/epilot/what-to-expect-when-you-join-epilot-1hc1</guid>
      <description>&lt;p&gt;Hey you, awesome engineer.&lt;/p&gt;

&lt;p&gt;Let's imagine that you are joining epilot tomorrow. Would you like to have a sneak peek at your nearest future?&lt;/p&gt;

&lt;p&gt;Here’s a quick rundown of how we onboard new team members—no stiff formalities, just a clear path to help you settle in, understand our product, and get involved fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Friendly Start&lt;/strong&gt;&lt;br&gt;
On the day one we’ll introduce you to your team lead and a dedicated onboarding buddy—think of them as your go-to guides for all things at epilot. Need pointers on our processes, product roadmap, or just want to figure out who to chat with about a certain topic? They’ve got you covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your First Week&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Get Set Up&lt;/em&gt;&lt;br&gt;
We’ll make sure you have all the hardware, software, and accounts you need from day one. We’ll give you a handy checklist so you can get everything in place without the guesswork. Of course, your onboa buddy is always there to help you with everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Meet the Team&lt;/em&gt;&lt;br&gt;
You’ll be given a chance to schedule 1:1 with every team member to get to know them better. Regardless of being remote-friendly company, we value real connections and friendly relationships, and make sure that everyone feels valued and supported.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Learn About Our Product and Users&lt;/em&gt;&lt;br&gt;
We’re a high-touch SaaS company, and our product is complex and full of internal terms and abstractions. It might feel overwhelming in the beginning, but don't worry, we've got you. You will be given lots of resources like recordings of product demos and docs about different topics about epilot. No rush—just learn at your own pace and ask all the questions you have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Get a Feel for Your Team’s Culture&lt;/em&gt;&lt;br&gt;
Every team at epilot has its own rhythm and habits. Hop into Slack conversations, daily stand-ups, and team meetings to get a sense of what’s going on and how decisions get made. And feel free to share your ideas and suggestions - we are always eager to improve.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Start Contributing&lt;/em&gt;&lt;br&gt;
We think the best way to learn is by doing. Early on, you’ll probably tackle a small assignment to get familiar with our systems and processes. Consider it your first real taste of epilot in action.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Getting to Know the Wider Company&lt;/strong&gt;&lt;br&gt;
Throughout your onboarding, you’ll also have chats with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Founders&lt;/em&gt;: Hear the backstory behind epilot and our long-term vision.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Department Heads&lt;/em&gt;: Whether it’s Sales, Marketing, Engineering, or Product, each leader will fill you in on how their team supports our bigger goals. The idea is to see how all the pieces fit together and create the vibe.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Engineering &amp;amp; Product Leads&lt;/em&gt;: If you love diving into tech details, these sessions will give you insights into our architecture, tools, and future product plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Seeing the Product in Action&lt;/strong&gt;&lt;br&gt;
Our platform is built around microservices, but don’t worry if that’s new to you. We’ll walk you through the basics. We also encourage you to check out how our customers actually use epilot—like checking live customer feedbacks. It’s a great way to see what’s working and where we can improve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Balance Between Work and Collaboration&lt;/strong&gt;&lt;br&gt;
At epilot, we keep things productive but also supportive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Team Events&lt;/em&gt;: You’ll get invites to company-wide announcements, product demos, company events, and more.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Open Communication&lt;/em&gt;: Slack, shared docs, and frequent check-ins mean you’ll never be left guessing.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Culture Matters&lt;/em&gt;: We appreciate everyone’s contribution, whether it’s project-related or just sharing something fun. We are the team, and this is what matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Looking Ahead&lt;/strong&gt;&lt;br&gt;
After a few weeks, you’ll have a good handle on how things work here—and you’ll know who to reach out to when you need help. We’ll likely ask for your thoughts on onboarding so we can keep improving the process for the future.&lt;/p&gt;

&lt;p&gt;If this relaxed but purposeful approach resonates with you, we look forward to hearing from you. At epilot, we want every new hire to find their footing, make meaningful contributions, and grow in their role—without all the usual corporate fluff.&lt;/p&gt;

&lt;p&gt;Happy to have you on board ✨.&lt;/p&gt;

</description>
      <category>onboarding</category>
      <category>culture</category>
      <category>workplace</category>
      <category>hiring</category>
    </item>
    <item>
      <title>Have you heard about the anterior midcingulate cortex?</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sun, 07 Jan 2024 20:45:07 +0000</pubDate>
      <link>https://dev.to/kate_astrid/have-you-heard-about-the-anterior-midcingulate-cortex-4dcd</link>
      <guid>https://dev.to/kate_astrid/have-you-heard-about-the-anterior-midcingulate-cortex-4dcd</guid>
      <description>&lt;p&gt;The anterior midcingulate cortex (aMCC) is an amazing area in the human brain that plays a vital role in decision-making, emotional processing, and even pain perception. More interestingly, it acts as a crucial network hub for those tough cost/benefit calculations that fuel our perseverance, willpower, and tenacity.&lt;/p&gt;

&lt;p&gt;Let's talk about facing challenges. Whether it's cracking complex equations or gearing up for a marathon, we all respond differently. Some might find the effort too overwhelming and step back, while others dig in their heels and push through without immediate rewards. This trait of persistence, increasingly spotlighted in research of the aCMM, correlates with better outcomes in health, academics, and career success.&lt;/p&gt;

&lt;p&gt;Now, here's where it gets really cool: when programming, especially during those head-scratching moments that we might not particularly enjoy, our aMCC gets super active. Ever found yourself in a coding conundrum, feeling frustrated to the point of wanting to quit? That's your aMCC in action, getting stronger. It's akin to facing a steep climb while running; challenging, maybe not enjoyable, but incredibly effective in building mental strength and resilience.&lt;/p&gt;

&lt;p&gt;So, remember, pushing through a tough coding problem does more than solve an issue on your screen. It's an aMCC workout, enhancing your brain's capacity to navigate complex tasks and manage emotions. It's a double win - improving your coding skills and boosting brain power, even when it feels tough at the moment =).&lt;/p&gt;

&lt;p&gt;Nice to know, right?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Common Cognitive Biases That May Affect Us As Developers</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sat, 16 Sep 2023 10:38:14 +0000</pubDate>
      <link>https://dev.to/kate_astrid/common-cognitive-biases-that-may-affect-us-as-developers-1gkm</link>
      <guid>https://dev.to/kate_astrid/common-cognitive-biases-that-may-affect-us-as-developers-1gkm</guid>
      <description>&lt;p&gt;As developers, we need to think straight to create solid, clean code. But hey, we're all human, and sometimes our brains make errors that can muddle up our decisions. Scientists call these errors cognitive biases. They can lead us down the wrong path, but they're actually part of how our brains handle the crazy amount of information we get all the time. Without these biases, it would be super tough to process everything logically.&lt;/p&gt;

&lt;p&gt;Still, it's important to keep these biases in check, as they can twist our view of reality and lead to not-so-great decisions. So, I thought I'd share some common biases that we, as developers, can bump into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Confirmation Bias: We might lean towards solutions or tech that fit with what we already know or believe. This can stop us from picking up something that could be even better.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anchoring Bias: We sometimes stick too much to the first piece of info we get (the "anchor") when we're making decisions. For instance, we might guess how complex a task is based on something similar we've done before, even if it's not really the same thing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dunning-Kruger Effect: If we're not that experienced, we might think we're better at coding than we really are. This can make us too confident and cause mistakes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Survivorship Bias: We might only focus on the projects or tech that did well and ignore the ones that flopped. This can make us too optimistic about how likely something is to succeed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not Invented Here (NIH) Syndrome: Sometimes we might dismiss solutions made by others and end up making new tools or frameworks when we could just use what's already there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recency Bias: We might pay too much attention to things that happened recently, like a bug we just ran into. This could skew our view of how reliable a project or tech really is.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So there you have it, folks – a quick rundown of some of the mental trip-ups we might face as developers. It's essential to remember that we're all susceptible to these biases, no matter how skilled or experienced we are. By being aware of them, we can work smarter, make better decisions, and create code that really shines. Keep coding, stay aware, and let's build great stuff together!&lt;/p&gt;

</description>
      <category>rationality</category>
      <category>cognitivebiases</category>
      <category>bias</category>
    </item>
    <item>
      <title>Ten Red Flags That Obstruct Systems Thinking</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sat, 16 Sep 2023 10:33:59 +0000</pubDate>
      <link>https://dev.to/kate_astrid/ten-obstacles-to-systems-thinking-540l</link>
      <guid>https://dev.to/kate_astrid/ten-obstacles-to-systems-thinking-540l</guid>
      <description>&lt;p&gt;Hey cool people! 👋&lt;/p&gt;

&lt;p&gt;Today I want to share with you a piece of useful knowledge about systems thinking that helps me analyze systems and their connection to each other and allows me to improve my understanding of any process I interact with, be it companies, code bases, or even human bodies.&lt;/p&gt;

&lt;p&gt;In general, systems thinking is an approach to understanding and solving complex problems by looking at the bigger picture. Instead of focusing on individual parts or events, systems thinking considers how those parts are interconnected and how they impact each other. This approach helps to identify the root causes of problems and create long-lasting solutions, rather than just addressing the symptoms. In simple terms, systems thinking is like looking at a whole forest rather than just individual trees, recognizing how everything is connected and influences each other.&lt;/p&gt;

&lt;p&gt;There are ten statements — red flags — that act as obstacles to systems thinking:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“Let’s fix it quick!”
While systems thinking doesn't demand slow problem-solving, hastily jumping into solutions without understanding the problem is counterproductive.&lt;/li&gt;
&lt;li&gt;“Just put a Band-Aid on it.”
Temporary fixes can hide symptoms while the underlying issue continues to harm the organization.&lt;/li&gt;
&lt;li&gt;“We need the budget finalized!”
Focusing on budgets and deadlines promotes linear thinking, moving away from systems thinking principles.&lt;/li&gt;
&lt;li&gt;“We must respond right away!”
Rushing to address problems encourages linear thinking, while calmly analyzing the situation fosters systematic thinking.&lt;/li&gt;
&lt;li&gt;“Who cares?”
Apathy hinders creativity and problem-solving, preventing organizational growth and effective solutions.&lt;/li&gt;
&lt;li&gt;“We need more information.”
Collecting data is important, but linear thinking arises when expecting data alone to solve problems without proper analysis and action.&lt;/li&gt;
&lt;li&gt;“You are overthinking things.”
Disagreements may be seen as overthinking. Systems thinking requires stretching beyond comfort zones and embracing complex solutions.&lt;/li&gt;
&lt;li&gt;“Forget the rest of the organization.”
Linear thinkers prioritize personal needs with win-lose solutions. Systems thinking seeks win-win solutions that benefit everyone.&lt;/li&gt;
&lt;li&gt;“We don’t want any conflict.”
Avoiding conflict at all costs can prevent addressing real problems. Confronting conflicts is necessary for effective problem-solving.&lt;/li&gt;
&lt;li&gt;“We will do it this way.”
Imposing one's will stifles creativity, innovation, and collaboration, hindering effective problem-solving and growth within the organization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I truly believe that systems thinking can introduce new perspectives, reveal hidden problems if they exist, and help existing processes to thrive and prosper 💃 .&lt;br&gt;
Lets's discuss it. Have you tried this approach? 👀&lt;/p&gt;

</description>
      <category>rationality</category>
      <category>systemsthinking</category>
      <category>cognitivebiases</category>
    </item>
    <item>
      <title>How to Maintain the Performance of a React Application on a Daily Basis</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Wed, 06 Sep 2023 07:42:59 +0000</pubDate>
      <link>https://dev.to/epilot/how-to-maintain-the-performance-of-a-react-application-on-a-daily-basis-304k</link>
      <guid>https://dev.to/epilot/how-to-maintain-the-performance-of-a-react-application-on-a-daily-basis-304k</guid>
      <description>&lt;p&gt;I'm a fast-paced person: I eat quickly, walk briskly, and make decisions in a snap. What I can't stand, though, is a slow app — especially one I'm working on. So, I always find myself compelled to scrutinise its performance: examining how many times it re-renders, the size of the DOM, and the speed of user interactions, among other things. Armed with this data, I incrementally improve the app's performance by refining the codebase in a targeted way.&lt;/p&gt;

&lt;p&gt;Now, I'd like to share some strategies that could help enhance your app's performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Be Mindful of the Hooks You Use in Each Component&lt;/strong&gt;: From my perspective, this is crucial. &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; are the most commonly used hooks, but sometimes they can be eliminated with a bit of refactoring, without sacrificing functionality. Fewer hooks mean fewer re-renders, which in turn means a faster app. Other noteworthy hooks are &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt;, which memoize values, storing them in memory. On a larger scale, this can have an impact on performance. The same goes for &lt;code&gt;React.memo()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine you have a component that tracks a user's first name, last name, and age. A naive approach could be to declare three separate &lt;code&gt;useState&lt;/code&gt; hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UserProfile = () =&amp;gt; {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [age, setAge] = useState(0);

  // Multiple function calls to update the user profile
  const updateUserProfile = () =&amp;gt; {
    setFirstName('John');
    setLastName('Doe');
    setAge(30);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{`${firstName} ${lastName}`}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Age: {age}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updateUserProfile}&amp;gt;Update Profile&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time each of those hooks is called, the component will re-render, even if it happens simultaneously. We could condense this into a single &lt;code&gt;useState&lt;/code&gt; by using an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UserProfile = () =&amp;gt; {
  const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    age: 0,
  });

  // Single function call to update the user profile
  const updateUserProfile = () =&amp;gt; {
    setUser({
      firstName: 'John',
      lastName: 'Doe',
      age: 30,
    });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{`${user.firstName} ${user.lastName}`}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Age: {user.age}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updateUserProfile}&amp;gt;Update Profile&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have only one hook, and only one re-render when the hook is called. Bingo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimise the Number of DOM Nodes&lt;/strong&gt;: I always ask myself, "Can this HTML tag be safely removed?" If the answer is yes, I remove it. Fewer nodes lead to better performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you find the code using empty &lt;code&gt;div&lt;/code&gt;s solely for styling purposes, consider using CSS Grid or Flexbox to achieve the same layout, thus reducing the number of DOM nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;div className="wrapper"&amp;gt;
  &amp;lt;div className="innerWrapper"&amp;gt;
    &amp;lt;Component /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

// After
&amp;lt;div className="optimizedWrapper"&amp;gt;
  &amp;lt;Component /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Favor Class-Based Styling Over Inline Styles&lt;/strong&gt;: Inline styles are considered new objects on each render, which can make React think something has changed, triggering unnecessary re-renders and affecting performance. It's also worth noting that maintaining styles in separate files helps keep the styling consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;div style={{ display: 'flex' }}&amp;gt;

// After
&amp;lt;div className='container'&amp;gt;

// In a separate css file: 
.container: {
  display: flex
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer Simple &lt;code&gt;div&lt;/code&gt;s to &lt;code&gt;Box&lt;/code&gt; from Material UI or Similar UI Libraries:&lt;/strong&gt; Native HTML elements generally offer better performance because they don't carry the overhead of additional React components, including lifecycle methods and context consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;Box p={2}&amp;gt;

// After
&amp;lt;div className="container"&amp;gt;

// In a separate css file: 
.container: {
  padding: '2px'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer Conditional Classes to Conditional Styles in &lt;code&gt;makeStyles&lt;/code&gt; from Material UI or Similar UI Libraries:&lt;/strong&gt; Using predefined classes that are toggled based on conditions can be more performant, as it doesn't generate new classes at runtime. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
const useStyles = makeStyles({
  root: {
    backgroundColor: props =&amp;gt; props.isActive ? 'blue' : 'white',
  },
});

// After
const className = isActive ? 'active' : 'inactive';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opt for Functional Components:&lt;/strong&gt; There are several compelling reasons for this. First, functional components are easier for build tools like Webpack to optimize through "tree shaking," which only includes the code that's actually being used in the final bundle. Second, functional components often result in a smaller component tree and fewer lifecycle methods, allowing React's diffing algorithm to make quicker updates to the DOM. Lastly, functional components use less memory than class components, which is especially beneficial in environments where memory is limited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
class MyComponent extends React.Component {
  state = { /* ... */ };
  componentDidMount() { /* ... */ }
  render() { /* ... */ }
}

// After
const MyComponent = () =&amp;gt; {
  const [state, setState] = useState(/* ... */);
  useEffect(() =&amp;gt; { /* ... */ }, []);
  return (/* ... */);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By giving these tips a go and making them a regular part of your coding routine, you're not just on the path to a speedier React app, but you're also gonna make things a whole lot smoother for your users. &lt;br&gt;
Let's create better apps together =).&lt;/p&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>css</category>
    </item>
  </channel>
</rss>
