[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"home-content":3,"home-posts":7},{"intro":4,"availability":5,"aboutMe":6},"Passionate about technology & leadership.","Not open to opportunities right now","I’m a passionate Lead Developer who thrives on solving complex problems with code. Whether it’s building intuitive UIs or working on scalable backend systems, I love being hands-on across the full stack. With extensive experience leading teams and delivering high-quality software solutions, I’m always open to new opportunities and collaborations.",{"items":8,"total":1332},[9,446,660],{"title":10,"slug":11,"date":12,"excerpt":13,"coverImage":14,"body":15,"tags":443},"Code Reviews, Quality, and All the Ways We Screw It Up","code-reviews","2025-08-16","Code quality isn’t about nitpicking style or chasing 100% test coverage—it’s about habits. Small PRs, constructive feedback, shared ownership, and learning from mistakes turn quality from a buzzword into a culture teams actually live by.","https://images.ctfassets.net/26iu5vjpoesw/4l6Mb1emgPs48ipoyfW7Gg/03725fdbd00a148c26b653765d4fda9d/volodymyr-dobrovolskyy-KrYbarbAx5s-unsplash.jpg",{"data":16,"content":17,"nodeType":442},{},[18,27,34,41,48,57,64,71,78,85,92,100,107,114,121,128,136,143,150,157,212,219,227,234,241,248,256,263,270,277,284,292,299,306,313,346,353,360,367,375,382,435],{"data":19,"content":20,"nodeType":26},{},[21],{"data":22,"marks":23,"value":24,"nodeType":25},{},[],"Everyone loves to talk about “quality.” It’s one of those words that gets tossed around in kickoff meetings and strategy docs like it’s obvious what it means. But when you actually sit down with engineers and ask, you get wildly different answers.","text","paragraph",{"data":28,"content":29,"nodeType":26},{},[30],{"data":31,"marks":32,"value":33,"nodeType":25},{},[],"One person thinks it means zero bugs in production. Another thinks it means code that’s easy to read. Someone else swears it’s about test coverage. Then there’s the person who just wants it to be easy to onboard new hires.",{"data":35,"content":36,"nodeType":26},{},[37],{"data":38,"marks":39,"value":40,"nodeType":25},{},[],"They’re all right. And that’s the problem. “Quality” is a moving target.",{"data":42,"content":43,"nodeType":26},{},[44],{"data":45,"marks":46,"value":47,"nodeType":25},{},[],"What I’ve learned is that quality isn’t something you get by decree. You don’t just say “We care about quality” and magically end up with a clean codebase. It’s something you build into your team’s habits - how you write, review, and ship code, and how you deal with screwups when (not if) they happen.",{"data":49,"content":50,"nodeType":26},{},[51],{"data":52,"marks":53,"value":56,"nodeType":25},{},[54],{"type":55},"bold","Automate the Bikeshedding",{"data":58,"content":59,"nodeType":26},{},[60],{"data":61,"marks":62,"value":63,"nodeType":25},{},[],"I once spent 15 minutes in a review arguing about whether snake_case or camelCase should be used for a local variable. The PR wasn’t even that interesting - it was fixing a bug in a log parser - but we were deep in the weeds debating naming like it was a philosophy seminar.",{"data":65,"content":66,"nodeType":26},{},[67],{"data":68,"marks":69,"value":70,"nodeType":25},{},[],"That was the moment I realised: people should not be wasting oxygen on this.",{"data":72,"content":73,"nodeType":26},{},[74],{"data":75,"marks":76,"value":77,"nodeType":25},{},[],"This is why linters and formatters exist. Let them be the bad cops. Humans should focus on whether the code makes sense, not whether it’s indented correctly.",{"data":79,"content":80,"nodeType":26},{},[81],{"data":82,"marks":83,"value":84,"nodeType":25},{},[],"If you’re in JavaScript, lean on Prettier and ESLint. Don’t invent a whole new style guide unless you want future engineers cursing your name in Slack. And if you do deviate, document why. “Because I said so” doesn’t cut it when a new hire has no idea why your project bans default exports.",{"data":86,"content":87,"nodeType":26},{},[88],{"data":89,"marks":90,"value":91,"nodeType":25},{},[],"The best teams I’ve worked with had their CI set up so if your PR failed linting, it didn’t even get reviewed. End of story. It sounds harsh, but it saves hours of nitpicking and keeps reviews focused on what matters.",{"data":93,"content":94,"nodeType":26},{},[95],{"data":96,"marks":97,"value":99,"nodeType":25},{},[98],{"type":55},"The Curse of the Monster PR",{"data":101,"content":102,"nodeType":26},{},[103],{"data":104,"marks":105,"value":106,"nodeType":25},{},[],"Here’s a story: one Friday afternoon, a teammate dropped a PR that was nearly 2,500 lines. A new feature and a major refactor, rolled into one. Nobody wanted to touch it. Reviewers skimmed, left a couple of half-hearted comments, and rubber-stamped it.",{"data":108,"content":109,"nodeType":26},{},[110],{"data":111,"marks":112,"value":113,"nodeType":25},{},[],"Guess what happened the next Monday? Production broke. The bug wasn’t subtle either - it was buried somewhere in the 2,500 lines of “trust me, it works.”",{"data":115,"content":116,"nodeType":26},{},[117],{"data":118,"marks":119,"value":120,"nodeType":25},{},[],"Since then, I’ve been ruthless about PR size. Small PRs get reviewed quickly, merged quickly, and if something goes wrong, they’re easy to roll back. Large PRs rot in limbo or worse, slip through with hidden landmines.",{"data":122,"content":123,"nodeType":26},{},[124],{"data":125,"marks":126,"value":127,"nodeType":25},{},[],"So: one logical change per PR. Add a feature, or clean up a function, or refactor an interface - not all three. If you need to make a monster change, break it into stacked PRs. Your future self (and your teammates) will thank you.",{"data":129,"content":130,"nodeType":26},{},[131],{"data":132,"marks":133,"value":135,"nodeType":25},{},[134],{"type":55},"Checklists Are Your Friend",{"data":137,"content":138,"nodeType":26},{},[139],{"data":140,"marks":141,"value":142,"nodeType":25},{},[],"Reviews are exhausting. If you’ve been cranking out features all day and someone throws a PR at you, it’s way too easy to glance at the diff, nod, and hit approve.",{"data":144,"content":145,"nodeType":26},{},[146],{"data":147,"marks":148,"value":149,"nodeType":25},{},[],"That’s how things slip through the cracks.",{"data":151,"content":152,"nodeType":26},{},[153],{"data":154,"marks":155,"value":156,"nodeType":25},{},[],"The teams I’ve seen succeed use checklists. Not long, bureaucratic ones - just a handful of prompts:",{"data":158,"content":159,"nodeType":211},{},[160,171,181,191,201],{"data":161,"content":162,"nodeType":170},{},[163],{"data":164,"content":165,"nodeType":26},{},[166],{"data":167,"marks":168,"value":169,"nodeType":25},{},[],"Does this match our architecture patterns?","list-item",{"data":172,"content":173,"nodeType":170},{},[174],{"data":175,"content":176,"nodeType":26},{},[177],{"data":178,"marks":179,"value":180,"nodeType":25},{},[],"Are there tests? Do they cover the edges?",{"data":182,"content":183,"nodeType":170},{},[184],{"data":185,"content":186,"nodeType":26},{},[187],{"data":188,"marks":189,"value":190,"nodeType":25},{},[],"Any obvious performance issues?",{"data":192,"content":193,"nodeType":170},{},[194],{"data":195,"content":196,"nodeType":26},{},[197],{"data":198,"marks":199,"value":200,"nodeType":25},{},[],"Security landmines?",{"data":202,"content":203,"nodeType":170},{},[204],{"data":205,"content":206,"nodeType":26},{},[207],{"data":208,"marks":209,"value":210,"nodeType":25},{},[],"Enough logging and metrics to debug later?","unordered-list",{"data":213,"content":214,"nodeType":26},{},[215],{"data":216,"marks":217,"value":218,"nodeType":25},{},[],"I once caught a major security flaw because of a checklist. A teammate had added logging… but was logging user passwords in plain text. Easy to miss if you’re skimming. The checklist forced me to stop and ask, “Wait, how are secrets handled here?” That one line item saved us from a nightmare.",{"data":220,"content":221,"nodeType":26},{},[222],{"data":223,"marks":224,"value":226,"nodeType":25},{},[225],{"type":55},"Feedback Without Ego",{"data":228,"content":229,"nodeType":26},{},[230],{"data":231,"marks":232,"value":233,"nodeType":25},{},[],"If you’ve ever gotten a review that felt like an attack, you know how demoralising it can be. I had a reviewer once leave comments like “This is garbage” and “Rewrite this, it’s wrong.” They weren’t wrong about the code needing improvement - but the way it was delivered made me dread pushing anything else.",{"data":235,"content":236,"nodeType":26},{},[237],{"data":238,"marks":239,"value":240,"nodeType":25},{},[],"The best reviewers I’ve worked with do the opposite. They treat reviews as collaboration, not judgment. They’ll say things like, “I had a hard time following this loop - could we simplify or add a comment?” Or, “What if we tried this other pattern to reduce complexity?”",{"data":242,"content":243,"nodeType":26},{},[244],{"data":245,"marks":246,"value":247,"nodeType":25},{},[],"It’s not about sugarcoating. It’s about keeping the focus on the code, not the person. If you destroy someone’s confidence, you don’t just slow them down - you slow the whole team.",{"data":249,"content":250,"nodeType":26},{},[251],{"data":252,"marks":253,"value":255,"nodeType":25},{},[254],{"type":55},"Don’t Hoard Knowledge",{"data":257,"content":258,"nodeType":26},{},[259],{"data":260,"marks":261,"value":262,"nodeType":25},{},[],"There’s a dangerous pattern where one person becomes the owner of a module. They’re the only one who knows how it works, so every change goes through them. At first, it feels efficient. Over time, it becomes a single point of failure.",{"data":264,"content":265,"nodeType":26},{},[266],{"data":267,"marks":268,"value":269,"nodeType":25},{},[],"I was on a team once where the only person who understood a critical service went on vacation. Of course, that was the week it blew up. The rest of us were flying blind because all the knowledge was locked in their head. We eventually fixed it, but it was painful and avoidable.",{"data":271,"content":272,"nodeType":26},{},[273],{"data":274,"marks":275,"value":276,"nodeType":25},{},[],"The fix is simple: rotate reviewers. Have people outside the “home team” of a module review changes. Let junior engineers shadow reviews and gradually step up. Build buddy systems so knowledge gets spread around.",{"data":278,"content":279,"nodeType":26},{},[280],{"data":281,"marks":282,"value":283,"nodeType":25},{},[],"A healthy codebase is one where no single person is a bottleneck.",{"data":285,"content":286,"nodeType":26},{},[287],{"data":288,"marks":289,"value":291,"nodeType":25},{},[290],{"type":55},"Mistakes Are Gold (If You Treat Them Right)",{"data":293,"content":294,"nodeType":26},{},[295],{"data":296,"marks":297,"value":298,"nodeType":25},{},[],"Stuff will break. Deploys will fail. Bugs will sneak past reviews. What matters is how you respond.",{"data":300,"content":301,"nodeType":26},{},[302],{"data":303,"marks":304,"value":305,"nodeType":25},{},[],"The worst thing you can do is start pointing fingers. I’ve seen teams spend more time arguing about who caused the outage than actually fixing it. That’s a culture killer.",{"data":307,"content":308,"nodeType":26},{},[309],{"data":310,"marks":311,"value":312,"nodeType":25},{},[],"The best teams run blameless postmortems. They ask:",{"data":314,"content":315,"nodeType":211},{},[316,326,336],{"data":317,"content":318,"nodeType":170},{},[319],{"data":320,"content":321,"nodeType":26},{},[322],{"data":323,"marks":324,"value":325,"nodeType":25},{},[],"How did our process let this happen?",{"data":327,"content":328,"nodeType":170},{},[329],{"data":330,"content":331,"nodeType":26},{},[332],{"data":333,"marks":334,"value":335,"nodeType":25},{},[],"Where was the gap in our testing, logging, or review?",{"data":337,"content":338,"nodeType":170},{},[339],{"data":340,"content":341,"nodeType":26},{},[342],{"data":343,"marks":344,"value":345,"nodeType":25},{},[],"What do we need to change so it doesn’t happen again?",{"data":347,"content":348,"nodeType":26},{},[349],{"data":350,"marks":351,"value":352,"nodeType":25},{},[],"I’ll never forget one outage we had that took down a core service for hours. The root cause wasn’t that someone “screwed up” - it was that our system made it way too easy to screw up silently. Once we reframed it like that, we fixed the system, not the person.",{"data":354,"content":355,"nodeType":26},{},[356],{"data":357,"marks":358,"value":359,"nodeType":25},{},[],"Same goes for retros. The most productive ones aren’t just “what went well, what didn’t.” They’re about experimenting: trying new review habits, new testing practices, new tooling, and seeing what sticks.",{"data":361,"content":362,"nodeType":26},{},[363],{"data":364,"marks":365,"value":366,"nodeType":25},{},[],"And when you ship something? Don’t treat it as “done.” Treat it as a hypothesis. Measure the impact, adjust, and move forward. Some experiments win, some fail - but both are learning.",{"data":368,"content":369,"nodeType":26},{},[370],{"data":371,"marks":372,"value":374,"nodeType":25},{},[373],{"type":55},"Closing Thought",{"data":376,"content":377,"nodeType":26},{},[378],{"data":379,"marks":380,"value":381,"nodeType":25},{},[],"Code quality isn’t about pedantic style wars or chasing arbitrary metrics. It’s about building habits where:",{"data":383,"content":384,"nodeType":211},{},[385,395,405,415,425],{"data":386,"content":387,"nodeType":170},{},[388],{"data":389,"content":390,"nodeType":26},{},[391],{"data":392,"marks":393,"value":394,"nodeType":25},{},[],"Tools handle the trivial stuff.",{"data":396,"content":397,"nodeType":170},{},[398],{"data":399,"content":400,"nodeType":26},{},[401],{"data":402,"marks":403,"value":404,"nodeType":25},{},[],"PRs stay small and reviewable.",{"data":406,"content":407,"nodeType":170},{},[408],{"data":409,"content":410,"nodeType":26},{},[411],{"data":412,"marks":413,"value":414,"nodeType":25},{},[],"Feedback is constructive.",{"data":416,"content":417,"nodeType":170},{},[418],{"data":419,"content":420,"nodeType":26},{},[421],{"data":422,"marks":423,"value":424,"nodeType":25},{},[],"Knowledge is shared.",{"data":426,"content":427,"nodeType":170},{},[428],{"data":429,"content":430,"nodeType":26},{},[431],{"data":432,"marks":433,"value":434,"nodeType":25},{},[],"Mistakes become lessons, not weapons.",{"data":436,"content":437,"nodeType":26},{},[438],{"data":439,"marks":440,"value":441,"nodeType":25},{},[],"If you get those right, quality stops being a buzzword. It becomes the default. Not because someone said so, but because the team wouldn’t work any other way.","document",[444,445],"best-practices","opinion",{"title":447,"slug":448,"date":449,"excerpt":450,"coverImage":451,"body":452,"tag":657,"tags":658},"My Everyday Carry: A Refined Blend of Function and Style","my-everyday-carry","2025-05-15","Over the past few weeks, I’ve been dialing in my Everyday Carry (EDC) setup, and I’ve finally landed on a gear combination that hits the sweet spot between functionality, durability, and style.","https://images.ctfassets.net/26iu5vjpoesw/7mUoNxFyELpTZejOIqK1mX/5f6c3ec535b0c71881f0054a5ad2e678/057D8942-E80F-4CDA-8A33-C25008E7A642.JPG",{"data":453,"content":454,"nodeType":442},{},[455,462,466,475,491,516,519,527,543,559,562,570,595,602,605,613,629,632,640],{"data":456,"content":457,"nodeType":26},{},[458],{"data":459,"marks":460,"value":461,"nodeType":25},{},[],"Over the past few weeks, I’ve been dialing in my Everyday Carry (EDC) setup, and I’ve finally landed on a gear combination that hits the sweet spot between functionality, durability, and style. I’m a believer in carrying only what I actually use, but I also think there’s room for a bit of personality in the tools we keep close. Here’s a breakdown of what’s currently in my pockets (and on my wrist).",{"data":463,"content":464,"nodeType":465},{},[],"hr",{"data":467,"content":468,"nodeType":474},{},[469],{"data":470,"marks":471,"value":473,"nodeType":25},{},[472],{"type":55},"1. iPhone 16 Pro Max – Spigen C1 Case in Classic Orange","heading-3",{"data":476,"content":477,"nodeType":26},{},[478,482,487],{"data":479,"marks":480,"value":481,"nodeType":25},{},[],"Let’s start with the centerpiece: my ",{"data":483,"marks":484,"value":486,"nodeType":25},{},[485],{"type":55},"iPhone 16 Pro Max",{"data":488,"marks":489,"value":490,"nodeType":25},{},[],". It’s a powerhouse of a phone with the camera capabilities and processing muscle to handle everything from day-to-day tasks to spontaneous content creation. What really makes it stand out, though, is the case.",{"data":492,"content":493,"nodeType":26},{},[494,498,503,507,512],{"data":495,"marks":496,"value":497,"nodeType":25},{},[],"I’m rocking the ",{"data":499,"marks":500,"value":502,"nodeType":25},{},[501],{"type":55},"Spigen C1 case",{"data":504,"marks":505,"value":506,"nodeType":25},{},[]," in ",{"data":508,"marks":509,"value":511,"nodeType":25},{},[510],{"type":55},"classic orange",{"data":513,"marks":514,"value":515,"nodeType":25},{},[],", and it’s become a bit of a signature piece for me. It’s a nod to the original iMac G3 days, with a translucent design that merges retro vibes with modern protection. The orange pops just enough to feel bold without being flashy, and it adds a nice layer of grip without bulking things up.",{"data":517,"content":518,"nodeType":465},{},[],{"data":520,"content":521,"nodeType":474},{},[522],{"data":523,"marks":524,"value":526,"nodeType":25},{},[525],{"type":55},"2. Apple Watch Ultra 2 – Orange Alpine Loop Strap",{"data":528,"content":529,"nodeType":26},{},[530,534,539],{"data":531,"marks":532,"value":533,"nodeType":25},{},[],"On my wrist, I’m wearing the ",{"data":535,"marks":536,"value":538,"nodeType":25},{},[537],{"type":55},"Apple Watch Ultra 2",{"data":540,"marks":541,"value":542,"nodeType":25},{},[],", and if I had to pick one piece of tech I can't leave home without, this would be it. Between fitness tracking, navigation, and staying connected when my phone’s buried in a jacket pocket, it’s become indispensable.",{"data":544,"content":545,"nodeType":26},{},[546,550,555],{"data":547,"marks":548,"value":549,"nodeType":25},{},[],"The ",{"data":551,"marks":552,"value":554,"nodeType":25},{},[553],{"type":55},"orange Alpine Loop",{"data":556,"marks":557,"value":558,"nodeType":25},{},[]," complements the rugged design of the Ultra 2 perfectly. Not only is it insanely durable (and great for workouts or hikes), but the orange theme ties everything in my EDC together. Plus, the high-contrast look makes it easy to pair with both casual and outdoor fits.",{"data":560,"content":561,"nodeType":465},{},[],{"data":563,"content":564,"nodeType":474},{},[565],{"data":566,"marks":567,"value":569,"nodeType":25},{},[568],{"type":55},"3. Apple AirTag – Orange Leather Key Ring",{"data":571,"content":572,"nodeType":26},{},[573,577,582,586,591],{"data":574,"marks":575,"value":576,"nodeType":25},{},[],"Keys? Check. Location? Always accounted for. I’ve tucked an ",{"data":578,"marks":579,"value":581,"nodeType":25},{},[580],{"type":55},"Apple AirTag",{"data":583,"marks":584,"value":585,"nodeType":25},{},[]," into an ",{"data":587,"marks":588,"value":590,"nodeType":25},{},[589],{"type":55},"orange leather key ring",{"data":592,"marks":593,"value":594,"nodeType":25},{},[],", and it’s been one of those “set it and forget it” pieces that gives me peace of mind. The leather key ring feels premium and softens nicely with use, and yes – it continues the orange theme I’ve accidentally committed to.",{"data":596,"content":597,"nodeType":26},{},[598],{"data":599,"marks":600,"value":601,"nodeType":25},{},[],"It’s a small thing, but knowing I can track down my keys in seconds is a quality-of-life upgrade I didn’t know I needed until I had it.",{"data":603,"content":604,"nodeType":465},{},[],{"data":606,"content":607,"nodeType":474},{},[608],{"data":609,"marks":610,"value":612,"nodeType":25},{},[611],{"type":55},"4. Pirna Slim RFID Blocking Wallet",{"data":614,"content":615,"nodeType":26},{},[616,620,625],{"data":617,"marks":618,"value":619,"nodeType":25},{},[],"Last but definitely not least, I carry a ",{"data":621,"marks":622,"value":624,"nodeType":25},{},[623],{"type":55},"Pirna slim RFID-blocking wallet",{"data":626,"marks":627,"value":628,"nodeType":25},{},[],". Minimalist, sleek, and practical, this wallet does exactly what I need it to: keep my cards and a bit of cash secure, while fitting discreetly in a front pocket. The RFID protection is a nice bonus in today’s hyper-connected world, and the slim profile ensures I’m not lugging around a brick in my jeans.",{"data":630,"content":631,"nodeType":465},{},[],{"data":633,"content":634,"nodeType":474},{},[635],{"data":636,"marks":637,"value":639,"nodeType":25},{},[638],{"type":55},"Final Thoughts",{"data":641,"content":642,"nodeType":26},{},[643,647,653],{"data":644,"marks":645,"value":646,"nodeType":25},{},[],"My EDC might not be the flashiest, but it’s ",{"data":648,"marks":649,"value":652,"nodeType":25},{},[650],{"type":651},"italic","me",{"data":654,"marks":655,"value":656,"nodeType":25},{},[]," – clean, functional, and with just enough flair to stand out. Whether it's the consistent orange accents or the smart balance of tech and utility, each piece plays a role in making everyday life a little smoother. If you're looking to dial in your own carry, my advice is this: start with what you actually use, and let form follow function. The style part? That’ll come naturally.","edc",[659],"personal",{"title":661,"slug":662,"date":663,"excerpt":664,"coverImage":665,"body":666,"tag":1325,"tags":1326},"Embracing Cypress for Comprehensive Testing","cypress-testing","2025-05-03","This week, I explored Cypress for both end-to-end and component testing in my Laravel, Vue, and Inertia app. Discover how this approach enhanced my development workflow.","https://images.ctfassets.net/26iu5vjpoesw/3htXQqBIqsl8eO1DHCGoFN/359a390b3e1708845c8f485b996111bc/Cypress_Testing_End_to_End.jpg",{"data":667,"content":668,"nodeType":442},{},[669,707,714,717,725,732,739,802,809,816,863,880,888,924,931,938,954,979,1003,1010,1017,1024,1274,1281,1288,1304,1311,1318],{"data":670,"content":671,"nodeType":26},{},[672,676,685,689,694,698,703],{"data":673,"marks":674,"value":675,"nodeType":25},{},[],"This week has been all about refining the testing strategy for my Laravel + Vue + Inertia app—and honestly, it’s been one of those productive deep-dives that makes you wonder why you didn’t do it sooner. I decided to go all-in on ",{"data":677,"content":679,"nodeType":684},{"uri":678},"https://www.cypress.io/",[680],{"data":681,"marks":682,"value":683,"nodeType":25},{},[],"Cypress","hyperlink",{"data":686,"marks":687,"value":688,"nodeType":25},{},[],", exploring both ",{"data":690,"marks":691,"value":693,"nodeType":25},{},[692],{"type":55},"end-to-end (E2E)",{"data":695,"marks":696,"value":697,"nodeType":25},{},[]," and ",{"data":699,"marks":700,"value":702,"nodeType":25},{},[701],{"type":55},"component testing",{"data":704,"marks":705,"value":706,"nodeType":25},{},[]," capabilities.",{"data":708,"content":709,"nodeType":26},{},[710],{"data":711,"marks":712,"value":713,"nodeType":25},{},[],"And spoiler alert: I’m not looking back at Laravel Dusk.",{"data":715,"content":716,"nodeType":465},{},[],{"data":718,"content":719,"nodeType":724},{},[720],{"data":721,"marks":722,"value":723,"nodeType":25},{},[],"Why Cypress?","heading-2",{"data":726,"content":727,"nodeType":26},{},[728],{"data":729,"marks":730,"value":731,"nodeType":25},{},[],"When I first started working with Laravel, Dusk seemed like the natural choice for browser testing. It integrates well with Laravel and can simulate user interactions. But as soon as I brought Vue and Inertia into the mix, things started to feel… clunky. Dusk is tightly coupled to Laravel’s backend, and working with modern, dynamic frontends became increasingly brittle.",{"data":733,"content":734,"nodeType":26},{},[735],{"data":736,"marks":737,"value":738,"nodeType":25},{},[],"Cypress offered me what Dusk didn’t:",{"data":740,"content":741,"nodeType":211},{},[742,757,772,787],{"data":743,"content":744,"nodeType":170},{},[745],{"data":746,"content":747,"nodeType":26},{},[748,753],{"data":749,"marks":750,"value":752,"nodeType":25},{},[751],{"type":55},"Frontend-first testing",{"data":754,"marks":755,"value":756,"nodeType":25},{},[],", which aligns beautifully with Vue components and SPA behavior.",{"data":758,"content":759,"nodeType":170},{},[760],{"data":761,"content":762,"nodeType":26},{},[763,768],{"data":764,"marks":765,"value":767,"nodeType":25},{},[766],{"type":55},"Visual feedback",{"data":769,"marks":770,"value":771,"nodeType":25},{},[],": The Cypress Test Runner shows you exactly what's happening in your app as the test runs.",{"data":773,"content":774,"nodeType":170},{},[775],{"data":776,"content":777,"nodeType":26},{},[778,783],{"data":779,"marks":780,"value":782,"nodeType":25},{},[781],{"type":55},"Fast dev loop",{"data":784,"marks":785,"value":786,"nodeType":25},{},[],": Changes are picked up instantly with auto-reload and no need to recompile the full Laravel app.",{"data":788,"content":789,"nodeType":170},{},[790],{"data":791,"content":792,"nodeType":26},{},[793,798],{"data":794,"marks":795,"value":797,"nodeType":25},{},[796],{"type":55},"Powerful debugging",{"data":799,"marks":800,"value":801,"nodeType":25},{},[],": You can time-travel through your test steps, inspect the DOM, and pinpoint exactly where things go wrong.",{"data":803,"content":804,"nodeType":474},{},[805],{"data":806,"marks":807,"value":808,"nodeType":25},{},[],"End-to-End Testing: Simulating Actual User Flows",{"data":810,"content":811,"nodeType":26},{},[812],{"data":813,"marks":814,"value":815,"nodeType":25},{},[],"I set up tests for full user journeys like:",{"data":817,"content":818,"nodeType":211},{},[819,830,841,852],{"data":820,"content":821,"nodeType":170},{},[822],{"data":823,"content":824,"nodeType":26},{},[825],{"data":826,"marks":827,"value":829,"nodeType":25},{},[828],{"type":55},"Logging in",{"data":831,"content":832,"nodeType":170},{},[833],{"data":834,"content":835,"nodeType":26},{},[836],{"data":837,"marks":838,"value":840,"nodeType":25},{},[839],{"type":55},"Navigating to a dashboard",{"data":842,"content":843,"nodeType":170},{},[844],{"data":845,"content":846,"nodeType":26},{},[847],{"data":848,"marks":849,"value":851,"nodeType":25},{},[850],{"type":55},"Creating a resource (e.g., a post)",{"data":853,"content":854,"nodeType":170},{},[855],{"data":856,"content":857,"nodeType":26},{},[858],{"data":859,"marks":860,"value":862,"nodeType":25},{},[861],{"type":55},"Logging out",{"data":864,"content":865,"nodeType":26},{},[866,870,876],{"data":867,"marks":868,"value":869,"nodeType":25},{},[],"With Cypress, I could stub API calls or hit my backend directly depending on the scenario. The ability to easily mock auth states or use actual logins with ",{"data":871,"marks":872,"value":875,"nodeType":25},{},[873],{"type":874},"code","cy.request()",{"data":877,"marks":878,"value":879,"nodeType":25},{},[]," opened up so many possibilities Dusk made painful.",{"data":881,"content":882,"nodeType":26},{},[883],{"data":884,"marks":885,"value":887,"nodeType":25},{},[886],{"type":55},"Example:",{"data":889,"content":922,"nodeType":923},{"target":890},{"metadata":891,"sys":894,"fields":910},{"tags":892,"concepts":893},[],[],{"space":895,"id":900,"type":901,"createdAt":902,"updatedAt":902,"environment":903,"publishedVersion":907,"revision":908,"locale":909},{"sys":896},{"type":897,"linkType":898,"id":899},"Link","Space","26iu5vjpoesw","2HdfBJjFxgqD8eS2IBT1dZ","Asset","2025-05-03T12:18:17.919Z",{"sys":904},{"id":905,"type":897,"linkType":906},"master","Environment",3,1,"en-US",{"title":911,"description":912,"file":913},"Screenshot 2025-05-03 at 13.17.51","",{"url":914,"details":915,"fileName":920,"contentType":921},"//images.ctfassets.net/26iu5vjpoesw/2HdfBJjFxgqD8eS2IBT1dZ/ab76cf72462651c7bbd2c537e6bd3e76/Screenshot_2025-05-03_at_13.17.51.png",{"size":916,"image":917},37105,{"width":918,"height":919},492,205,"Screenshot 2025-05-03 at 13.17.51.png","image/png",[],"embedded-asset-block",{"data":925,"content":926,"nodeType":26},{},[927],{"data":928,"marks":929,"value":930,"nodeType":25},{},[],"It’s readable, runs fast, and tells me exactly what went wrong when things fail.",{"data":932,"content":933,"nodeType":474},{},[934],{"data":935,"marks":936,"value":937,"nodeType":25},{},[],"Component Testing: Testing Vue Bits in Isolation",{"data":939,"content":940,"nodeType":26},{},[941,945,950],{"data":942,"marks":943,"value":944,"nodeType":25},{},[],"I’m using Vue/Nuxt 3 (with Vite), and Cypress has a built-in mode for ",{"data":946,"marks":947,"value":949,"nodeType":25},{},[948],{"type":55},"mounting individual components",{"data":951,"marks":952,"value":953,"nodeType":25},{},[]," without spinning up the full app.",{"data":955,"content":956,"nodeType":26},{},[957,961,966,970,975],{"data":958,"marks":959,"value":960,"nodeType":25},{},[],"Testing things like a ",{"data":962,"marks":963,"value":965,"nodeType":25},{},[964],{"type":874},"Modal.vue",{"data":967,"marks":968,"value":969,"nodeType":25},{},[]," or ",{"data":971,"marks":972,"value":974,"nodeType":25},{},[973],{"type":874},"Button.vue",{"data":976,"marks":977,"value":978,"nodeType":25},{},[]," component became as simple as:",{"data":980,"content":1002,"nodeType":923},{"target":981},{"metadata":982,"sys":985,"fields":992},{"tags":983,"concepts":984},[],[],{"space":986,"id":988,"type":901,"createdAt":989,"updatedAt":989,"environment":990,"publishedVersion":907,"revision":908,"locale":909},{"sys":987},{"type":897,"linkType":898,"id":899},"19xlRFH8245xGx6EtquqTv","2025-05-03T12:18:49.955Z",{"sys":991},{"id":905,"type":897,"linkType":906},{"title":993,"description":912,"file":994},"Screenshot 2025-05-03 at 13.18.35",{"url":995,"details":996,"fileName":1001,"contentType":921},"//images.ctfassets.net/26iu5vjpoesw/19xlRFH8245xGx6EtquqTv/9a4a7241c7f594bd17a4f204311a4e6a/Screenshot_2025-05-03_at_13.18.35.png",{"size":997,"image":998},34250,{"width":999,"height":1000},403,328,"Screenshot 2025-05-03 at 13.18.35.png",[],{"data":1004,"content":1005,"nodeType":26},{},[1006],{"data":1007,"marks":1008,"value":1009,"nodeType":25},{},[],"This gave me quick feedback without needing to navigate through my app’s UI or backend. I could catch layout or logic bugs directly at the component level.",{"data":1011,"content":1012,"nodeType":724},{},[1013],{"data":1014,"marks":1015,"value":1016,"nodeType":25},{},[],"Cypress vs Laravel Dusk",{"data":1018,"content":1019,"nodeType":26},{},[1020],{"data":1021,"marks":1022,"value":1023,"nodeType":25},{},[],"Here’s a breakdown of how Cypress compares to Dusk from my personal experience:",{"data":1025,"content":1026,"nodeType":1273},{},[1027,1061,1095,1129,1163,1197,1231],{"data":1028,"content":1029,"nodeType":1060},{},[1030,1041,1051],{"data":1031,"content":1032,"nodeType":1040},{},[1033],{"data":1034,"content":1035,"nodeType":26},{},[1036],{"data":1037,"marks":1038,"value":1039,"nodeType":25},{},[],"Feature","table-cell",{"data":1042,"content":1043,"nodeType":1040},{},[1044],{"data":1045,"content":1046,"nodeType":26},{},[1047],{"data":1048,"marks":1049,"value":1050,"nodeType":25},{},[],"Laravel Dusk",{"data":1052,"content":1053,"nodeType":1040},{},[1054],{"data":1055,"content":1056,"nodeType":26},{},[1057],{"data":1058,"marks":1059,"value":683,"nodeType":25},{},[],"table-row",{"data":1062,"content":1063,"nodeType":1060},{},[1064,1075,1085],{"data":1065,"content":1066,"nodeType":1040},{},[1067],{"data":1068,"content":1069,"nodeType":26},{},[1070],{"data":1071,"marks":1072,"value":1074,"nodeType":25},{},[1073],{"type":55},"Frontend integration",{"data":1076,"content":1077,"nodeType":1040},{},[1078],{"data":1079,"content":1080,"nodeType":26},{},[1081],{"data":1082,"marks":1083,"value":1084,"nodeType":25},{},[],"Poor",{"data":1086,"content":1087,"nodeType":1040},{},[1088],{"data":1089,"content":1090,"nodeType":26},{},[1091],{"data":1092,"marks":1093,"value":1094,"nodeType":25},{},[],"Excellent",{"data":1096,"content":1097,"nodeType":1060},{},[1098,1109,1119],{"data":1099,"content":1100,"nodeType":1040},{},[1101],{"data":1102,"content":1103,"nodeType":26},{},[1104],{"data":1105,"marks":1106,"value":1108,"nodeType":25},{},[1107],{"type":55},"Vue/SPA support",{"data":1110,"content":1111,"nodeType":1040},{},[1112],{"data":1113,"content":1114,"nodeType":26},{},[1115],{"data":1116,"marks":1117,"value":1118,"nodeType":25},{},[],"Limited",{"data":1120,"content":1121,"nodeType":1040},{},[1122],{"data":1123,"content":1124,"nodeType":26},{},[1125],{"data":1126,"marks":1127,"value":1128,"nodeType":25},{},[],"Native",{"data":1130,"content":1131,"nodeType":1060},{},[1132,1143,1153],{"data":1133,"content":1134,"nodeType":1040},{},[1135],{"data":1136,"content":1137,"nodeType":26},{},[1138],{"data":1139,"marks":1140,"value":1142,"nodeType":25},{},[1141],{"type":55},"Live reloading",{"data":1144,"content":1145,"nodeType":1040},{},[1146],{"data":1147,"content":1148,"nodeType":26},{},[1149],{"data":1150,"marks":1151,"value":1152,"nodeType":25},{},[],"No",{"data":1154,"content":1155,"nodeType":1040},{},[1156],{"data":1157,"content":1158,"nodeType":26},{},[1159],{"data":1160,"marks":1161,"value":1162,"nodeType":25},{},[],"Yes",{"data":1164,"content":1165,"nodeType":1060},{},[1166,1177,1187],{"data":1167,"content":1168,"nodeType":1040},{},[1169],{"data":1170,"content":1171,"nodeType":26},{},[1172],{"data":1173,"marks":1174,"value":1176,"nodeType":25},{},[1175],{"type":55},"Debuggability",{"data":1178,"content":1179,"nodeType":1040},{},[1180],{"data":1181,"content":1182,"nodeType":26},{},[1183],{"data":1184,"marks":1185,"value":1186,"nodeType":25},{},[],"Console logs",{"data":1188,"content":1189,"nodeType":1040},{},[1190],{"data":1191,"content":1192,"nodeType":26},{},[1193],{"data":1194,"marks":1195,"value":1196,"nodeType":25},{},[],"Time travel UI",{"data":1198,"content":1199,"nodeType":1060},{},[1200,1211,1221],{"data":1201,"content":1202,"nodeType":1040},{},[1203],{"data":1204,"content":1205,"nodeType":26},{},[1206],{"data":1207,"marks":1208,"value":1210,"nodeType":25},{},[1209],{"type":55},"Speed",{"data":1212,"content":1213,"nodeType":1040},{},[1214],{"data":1215,"content":1216,"nodeType":26},{},[1217],{"data":1218,"marks":1219,"value":1220,"nodeType":25},{},[],"Slower",{"data":1222,"content":1223,"nodeType":1040},{},[1224],{"data":1225,"content":1226,"nodeType":26},{},[1227],{"data":1228,"marks":1229,"value":1230,"nodeType":25},{},[],"Faster",{"data":1232,"content":1233,"nodeType":1060},{},[1234,1245,1255],{"data":1235,"content":1236,"nodeType":1040},{},[1237],{"data":1238,"content":1239,"nodeType":26},{},[1240],{"data":1241,"marks":1242,"value":1244,"nodeType":25},{},[1243],{"type":55},"Mocking/Interception",{"data":1246,"content":1247,"nodeType":1040},{},[1248],{"data":1249,"content":1250,"nodeType":26},{},[1251],{"data":1252,"marks":1253,"value":1254,"nodeType":25},{},[],"Difficult",{"data":1256,"content":1257,"nodeType":1040},{},[1258,1265],{"data":1259,"content":1260,"nodeType":26},{},[1261],{"data":1262,"marks":1263,"value":1264,"nodeType":25},{},[],"Easy with ",{"data":1266,"content":1267,"nodeType":26},{},[1268],{"data":1269,"marks":1270,"value":1272,"nodeType":25},{},[1271],{"type":874},"cy.intercept()","table",{"data":1275,"content":1276,"nodeType":26},{},[1277],{"data":1278,"marks":1279,"value":1280,"nodeType":25},{},[],"I still think Dusk has its place—like for backend-heavy flows or where you want to test actual browser behavior end-to-end in a true Laravel context. But for any serious Vue-heavy app, Cypress just feels like the tool Laravel didn’t know it needed.",{"data":1282,"content":1283,"nodeType":724},{},[1284],{"data":1285,"marks":1286,"value":1287,"nodeType":25},{},[],"Why It Matters",{"data":1289,"content":1290,"nodeType":26},{},[1291,1295,1300],{"data":1292,"marks":1293,"value":1294,"nodeType":25},{},[],"As developers, we often skip tests when the tools don’t feel intuitive. That was me with Dusk. But Cypress made testing feel like ",{"data":1296,"marks":1297,"value":1299,"nodeType":25},{},[1298],{"type":55},"part of the dev workflow",{"data":1301,"marks":1302,"value":1303,"nodeType":25},{},[]," rather than a chore. And the confidence I’ve gained by knowing both my components and entire flows are solid? Game-changing.",{"data":1305,"content":1306,"nodeType":26},{},[1307],{"data":1308,"marks":1309,"value":1310,"nodeType":25},{},[],"If you're building apps with Laravel + Vue + Inertia—or really any modern frontend—Cypress is worth your time.",{"data":1312,"content":1313,"nodeType":26},{},[1314],{"data":1315,"marks":1316,"value":1317,"nodeType":25},{},[],"This week was just the start. I’ve already planned more tests, more component coverage, and even experimenting with CI integration next.",{"data":1319,"content":1320,"nodeType":26},{},[1321],{"data":1322,"marks":1323,"value":1324,"nodeType":25},{},[],"Happy testing 👨‍💻","Laravel",[1327,1328,1329,1330,1331],"testing","laravel","vue","cypress","tutorial",8]