[{"data":1,"prerenderedAt":360},["ShallowReactive",2],{"navigation":3,"blog-page":26,"blogs":36},[4],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Blog","\u002Fblog","blog",[9,13,17,21],{"title":10,"path":11,"stem":12},"How I Built a Reusable Component Library from Scratch","\u002Fblog\u002Fbuilding-a-reusable-component-library","blog\u002Fbuilding-a-reusable-component-library",{"title":14,"path":15,"stem":16},"From Idea to Production: My End-to-End Engineering Process","\u002Fblog\u002Ffrom-idea-to-production","blog\u002Ffrom-idea-to-production",{"title":18,"path":19,"stem":20},"Web Performance Is a Feature","\u002Fblog\u002Fweb-performance-is-a-feature","blog\u002Fweb-performance-is-a-feature",{"title":22,"path":23,"stem":24},"The Case for Boring Technology and Maintainable Code","\u002Fblog\u002Fwriting-maintainable-code","blog\u002Fwriting-maintainable-code",false,{"id":27,"title":28,"body":29,"description":30,"extension":31,"links":29,"meta":32,"navigation":33,"path":6,"seo":34,"stem":7,"__hash__":35},"pages\u002Fblog.yml","Latest Articles",null,"Some of my recent thoughts on software engineering, architecture, and the tech industry.","yml",{},true,{"title":28,"description":30},"L1BkkI0Y9JXy32gDsa6l-Z-WdBQf4Hp1eJCs95Kx06c",[37,221,273,325],{"id":38,"title":14,"author":39,"body":43,"color":211,"date":212,"description":213,"extension":214,"icon":215,"image":29,"layout":216,"meta":217,"minRead":218,"navigation":33,"path":15,"seo":219,"stem":16,"__hash__":220},"blog\u002Fblog\u002Ffrom-idea-to-production.md",{"name":40,"avatar":41},"Rong Yuthean",{"src":42,"alt":40},"\u002Fprofile.jpg",{"type":44,"value":45,"toc":196},"minimark",[46,50,53,58,61,66,69,75,78,82,85,98,102,105,108,112,115,130,133,137,140,144,147,151,162,166,193],[47,48,49],"p",{},"Shipping good software isn't about following a rigid formula. It's about having a flexible process that adapts to each project while keeping quality high. After refining my approach across dozens of projects, I've landed on a workflow that consistently delivers reliable results without slowing teams down.",[47,51,52],{},"In this article, I'll walk through my end-to-end process, from understanding requirements to running code in production, using my work on the EcoTrack ingestion service as a case study.",[54,55,57],"h2",{"id":56},"phase-1-understanding-the-problem","Phase 1: Understanding the Problem",[47,59,60],{},"Every successful system starts with a clear understanding of the problem. For EcoTrack, the challenge was ingesting millions of sustainability events per day from mobile clients, without losing data and while keeping the API responsive.",[62,63,65],"h3",{"id":64},"gathering-requirements","Gathering Requirements",[47,67,68],{},"I started by talking to product, mobile, and data teams to understand the real constraints: expected traffic, peak loads, acceptable latency, and how fresh the data needed to be for downstream reports.",[70,71,72],"blockquote",{},[47,73,74],{},"\"We don't need real-time dashboards, but we can never lose an event.\" — Data team lead",[47,76,77],{},"That single sentence shaped the entire architecture.",[62,79,81],{"id":80},"defining-success","Defining Success",[47,83,84],{},"Before writing any code, we agreed on measurable goals:",[86,87,88,92,95],"ul",{},[89,90,91],"li",{},"p95 API latency under 100 ms at peak load",[89,93,94],{},"Zero data loss, with at-least-once delivery",[89,96,97],{},"New engineers able to ship a change in their first week",[54,99,101],{"id":100},"phase-2-technical-design","Phase 2: Technical Design",[47,103,104],{},"I wrote a short design document covering the architecture, data model, API contracts, failure modes, and alternatives we considered. The final design separated a thin, stateless API layer from asynchronous workers connected by a durable queue.",[47,106,107],{},"Sharing the design early surfaced issues that would have been expensive to fix later, like a missing idempotency key on the event endpoint.",[54,109,111],{"id":110},"phase-3-building-in-small-increments","Phase 3: Building in Small Increments",[47,113,114],{},"Instead of one big launch, I broke the work into small, independently deployable pieces:",[116,117,118,121,124,127],"ol",{},[89,119,120],{},"A walking skeleton: endpoint, queue, worker, and database, end to end",[89,122,123],{},"Validation, idempotency, and retries",[89,125,126],{},"Batching and backpressure for throughput",[89,128,129],{},"Dashboards, alerts, and runbooks",[47,131,132],{},"Every pull request was small, reviewed, and covered by unit and integration tests running in CI.",[54,134,136],{"id":135},"phase-4-testing-under-real-conditions","Phase 4: Testing Under Real Conditions",[47,138,139],{},"Unit tests catch logic bugs, but distributed systems fail in more interesting ways. I added load tests that replayed production-like traffic and chaos tests that killed workers mid-batch to verify we never dropped events.",[54,141,143],{"id":142},"phase-5-release-observability","Phase 5: Release & Observability",[47,145,146],{},"We rolled out behind a feature flag, starting with 1% of traffic and gradually increasing while watching latency, error rates, and queue depth. Structured logs, metrics, and distributed tracing made it easy to understand exactly what the system was doing.",[54,148,150],{"id":149},"results","Results",[86,152,153,156,159],{},[89,154,155],{},"p95 latency dropped from 450 ms to 80 ms",[89,157,158],{},"Zero lost events since launch",[89,160,161],{},"Infrastructure costs fell by 35% thanks to batching",[54,163,165],{"id":164},"key-takeaways","Key Takeaways",[86,167,168,175,181,187],{},[89,169,170,174],{},[171,172,173],"strong",{},"Clarify constraints first."," The right architecture falls out of the real requirements.",[89,176,177,180],{},[171,178,179],{},"Write it down."," A two-page design doc saves weeks of rework.",[89,182,183,186],{},[171,184,185],{},"Ship small."," Small changes are easier to review, test, and roll back.",[89,188,189,192],{},[171,190,191],{},"Observe everything."," You can't fix what you can't see.",[47,194,195],{},"Great engineering is less about clever code and more about a disciplined process that makes reliable outcomes the default.",{"title":197,"searchDepth":198,"depth":198,"links":199},"",2,[200,205,206,207,208,209,210],{"id":56,"depth":198,"text":57,"children":201},[202,204],{"id":64,"depth":203,"text":65},3,{"id":80,"depth":203,"text":81},{"id":100,"depth":198,"text":101},{"id":110,"depth":198,"text":111},{"id":135,"depth":198,"text":136},{"id":142,"depth":198,"text":143},{"id":149,"depth":198,"text":150},{"id":164,"depth":198,"text":165},"#10b981","2025-04-23","A detailed breakdown of how I take a feature from requirements to a reliable production release, with practical tips for engineers at every stage.","md","i-lucide-rocket","list",{},8,{"title":14,"description":213},"drh2O89XpebvLvGHI5XivRQWWWyi0zO6dyW1jhhW4CE",{"id":222,"title":18,"author":223,"body":225,"color":264,"date":265,"description":266,"extension":214,"icon":267,"image":29,"layout":268,"meta":269,"minRead":270,"navigation":33,"path":19,"seo":271,"stem":20,"__hash__":272},"blog\u002Fblog\u002Fweb-performance-is-a-feature.md",{"name":40,"avatar":224},{"src":42,"alt":40},{"type":44,"value":226,"toc":262},[227,230,233,236,239,242,259],[47,228,229],{},"Performance is often treated as a nice-to-have, something to fix \"later\". After a series of experiments on the Wavelength music web app, I've seen first-hand how much speed directly impacts user behavior.",[47,231,232],{},"When we first launched, our homepage took over four seconds to become interactive on mid-range mobile devices. Our engagement metrics were underwhelming, so I proposed treating performance as a product feature with its own goals and dashboards.",[47,234,235],{},"We started by collecting real-user metrics (Core Web Vitals) instead of relying only on lab tests on fast laptops. Then we tackled the biggest wins: code-splitting large routes, server-side rendering critical pages, lazy-loading images, and caching API responses at the edge.",[47,237,238],{},"The results were striking: cutting Largest Contentful Paint from 4.1 s to 1.6 s increased session starts by 34%. Even more interesting, users on slower networks saw the biggest gains, showing that performance work is also accessibility work.",[47,240,241],{},"I've since adopted a simple framework for performance:",[116,243,244,247,250,253,256],{},[89,245,246],{},"Measure real users, not just your own machine",[89,248,249],{},"Set performance budgets and enforce them in CI",[89,251,252],{},"Fix the biggest bottleneck first, then measure again",[89,254,255],{},"Make performance visible to the whole team with dashboards",[89,257,258],{},"Treat regressions like bugs, not nice-to-haves",[47,260,261],{},"The most valuable lesson I've learned is that fast software feels better, and users notice even when they can't explain why.",{"title":197,"searchDepth":198,"depth":198,"links":263},[],"#f59e0b","2025-03-15","How measuring and optimizing real-user performance metrics directly improved engagement, conversion, and the overall user experience.","i-lucide-gauge","dashboard",{},5,{"title":18,"description":266},"oM3OxgJSz0maKN3YGiPgh6D4cO_KqPBp30cN24nHoCQ",{"id":274,"title":10,"author":275,"body":277,"color":316,"date":317,"description":318,"extension":214,"icon":319,"image":29,"layout":320,"meta":321,"minRead":322,"navigation":33,"path":11,"seo":323,"stem":12,"__hash__":324},"blog\u002Fblog\u002Fbuilding-a-reusable-component-library.md",{"name":40,"avatar":276},{"src":42,"alt":40},{"type":44,"value":278,"toc":314},[279,282,285,288,291,305,308,311],[47,280,281],{},"After years of copying the same components between projects, I finally built a proper shared component library. The process was challenging but incredibly rewarding, and I want to share my approach for other engineers considering the same journey.",[47,283,284],{},"I started by auditing five of our applications, looking for duplicated components and patterns. The results were eye-opening: seven slightly different button implementations, three date pickers, and inconsistent handling of loading and error states.",[47,286,287],{},"Rather than designing everything upfront, I built the library iteratively alongside a real product. Each component was extracted only once it had proven itself in production code.",[47,289,290],{},"The core of the library includes:",[86,292,293,296,299,302],{},[89,294,295],{},"Strongly typed components with TypeScript props and emitted events",[89,297,298],{},"Design tokens exposed as CSS variables for theming",[89,300,301],{},"Accessibility built in, with keyboard navigation and ARIA attributes tested automatically",[89,303,304],{},"Visual regression tests and auto-generated documentation",[47,306,307],{},"The biggest challenge wasn't technical but organizational: getting teams to trust and adopt a shared dependency. Semantic versioning, clear changelogs, and codemods for breaking changes made upgrades painless.",[47,309,310],{},"The payoff has been enormous: new features ship roughly 40% faster, UI bugs have dropped significantly, and every app now looks and behaves consistently.",[47,312,313],{},"If you're considering building your own library, start small with your most-used components, test them in real products, and automate everything you can. A good component library should feel like a trusted teammate, not a set of restrictions.",{"title":197,"searchDepth":198,"depth":198,"links":315},[],"#8b5cf6","2025-03-05","A practical guide to creating a shared, type-safe component library, from initial audit to publishing, and the lessons learned along the way.","i-lucide-blocks","grid",{},6,{"title":10,"description":318},"IXFwShXjMdisclgIXRJ0eIYjlbaTcoMEMnrlitGpKmA",{"id":326,"title":22,"author":327,"body":329,"color":351,"date":352,"description":353,"extension":214,"icon":354,"image":29,"layout":355,"meta":356,"minRead":357,"navigation":33,"path":23,"seo":358,"stem":24,"__hash__":359},"blog\u002Fblog\u002Fwriting-maintainable-code.md",{"name":40,"avatar":328},{"src":42,"alt":40},{"type":44,"value":330,"toc":349},[331,334,337,340,343,346],[47,332,333],{},"I recently inherited a project that challenged how I think about technical choices. It used five different databases, two message brokers, and a custom framework, all for an application serving a few thousand users.",[47,335,336],{},"It got me thinking about the value of \"boring technology\": proven tools whose failure modes are well understood, so your team can spend its innovation budget on the problems that actually matter to the business.",[47,338,339],{},"Over three months, we consolidated onto PostgreSQL and a single queue, removed the custom framework in favor of well-documented open-source libraries, and added tests around every behavior before we changed it.",[47,341,342],{},"The results were remarkable: deployment time dropped from an hour to eight minutes, on-call incidents fell by 70%, and new engineers were shipping changes in their first week instead of their first month.",[47,344,345],{},"I now ask a few questions before adopting any new tool: \"What problem does this solve that we can't solve today? Who will maintain it at 3 a.m.? How will the next engineer understand it?\"",[47,347,348],{},"In our rush to adopt the newest thing, it's easy to forget that the most valuable code is often the simplest: code that's easy to read, easy to test, and easy to change.",{"title":197,"searchDepth":198,"depth":198,"links":350},[],"#0ea5e9","2025-01-28","Why choosing simple, proven tools and writing code for the next reader leads to more reliable systems and happier teams.","i-lucide-code","chat",{},7,{"title":22,"description":353},"a01srUcEVzal5DqpcuDu_9ZnVFJUe9657-m_ThP9TRg",1793207677057]