Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

WebDevPro

72 Articles
Kinnari Chohan
19 Jan 2026
9 min read
Save for later

WebDevPro #123: Looking Back at 2025 and January in Focus

Kinnari Chohan
19 Jan 2026
9 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #123 Looking Back at 2025 and January in Focus Crafting the Web: Tips, Tools, and Trends for Developers Hi , Welcome back to the first WebDevPro issue of the year. We’re reopening with a tighter promise: a sharper take on what’s happening in web development, and what’s actually worth your attention. The web moved fast in 2025, but the real story was the direction the updates were pointed in. Everyday development got more structured, more AI-assisted, and more performance-aware, and the teams that adapted early are already moving differently. Last year we sent 42 issues to 40,000+ subscribers. This year, we want each edition to feel useful within the first minute: the ideas you can carry into your codebase, the shifts that change how you build, and the updates you should act on before they turn into surprises. This edition is a reset and a catch-up in one. We’re bringing back pieces from past issues that still hold up when you’re dealing with async complexity, messy failure modes, and production AI features. Then we compress 2025 into the shifts that mattered, followed by a short news corner to get you current. Let’s get started! Most popular articles from our past newsletters Taming Asynchronous Control Flow in Node.js: From Callback Hell to Elegant Patterns (Originally sent: August 06, 2025) Starting from the classic “callback hell” problem, this article showed how simple techniques like early returns and modular functions can clean up asynchronous code. It then introduced essential async control patterns, including sequential execution, concurrency, race condition prevention, and limited parallelism using queues, all grounded in practical Node.js examples. Even as async and await improve readability, the underlying coordination problems have not gone away. APIs are more distributed, users are more impatient, and systems are more parallel. Knowing how to control flow, limit concurrency, and avoid race conditions remains critical for building resilient applications. Rethinking Error Handling with Information Architecture (Originally sent: September 15, 2025) Errors are not all the same, and this article made a strong case for treating them differently. By classifying errors into user, system, and contextual types, it reframed error handling as an information architecture and UX problem rather than a purely technical one. The piece explored how clear messaging, thoughtful fallback navigation, and consistent structure help users recover when things go wrong, without losing trust or orientation. As products grow more complex and AI-driven systems introduce new failure modes, error states have become more visible and more consequential. Designing for breakdowns, empty states, and partial failures is now a core part of resilient UX. This article’s focus on graceful degradation, user-centered language, and recovery paths feels increasingly relevant in a world where systems fail in subtle, contextual ways rather than simply crashing. Building an AI Email Enhancer with React 19 and Vercel AI SDK (Originally sent: September 29, 2025) This article explored how modern React and AI tooling come together in a real-world feature. It walked through building a type-safe AI email enhancer using React 19 Server Functions, useActionState, Zod validation, and the Vercel AI SDK. Beyond the mechanics, it showed how server-first form handling and structured AI responses can turn raw input into polished communication with minimal friction. As AI features move from experimentation into core product workflows, reliability and developer experience have become non-negotiable. This piece focused on exactly that. By emphasizing type safety, server-side execution, and predictable form behavior, it anticipated a shift toward AI integrations that are trustworthy, maintainable, and production-ready. In 2026, this approach feels less like a demo and more like the baseline for serious AI-powered web apps. The shifts that defined 2025 TypeScript became GitHub’s #1 language: In August 2025, TypeScript crossed a symbolic threshold: it became the most used language on GitHub by contributor counts, overtaking both Python and JavaScript. TypeScript isn’t “just nicer types” anymore. It’s the default foundation for modern web app scale, and it fits the direction frameworks and AI-assisted coding are already pulling teams toward: more structure, fewer runtime surprises, and codebases that stay maintainable as they grow. AI became a baseline developer skill, not a differentiator:In August 2025, TypeScript crossed a symbolic threshold: it became the most used language on GitHub by contributor counts, overtaking both Python and JavaScript. TypeScript isn’t “just nicer types” anymore. It’s the default foundation for modern web app scale, and it fits the direction frameworks and AI-assisted coding are already pulling teams toward: more structure, fewer runtime surprises, and codebases that stay maintainable as they grow. WebAssembly 2.0 made the browser a serious performance platform: With Wasm 2.0 marked “official,” the story is less “new tech” and more “this is stable enough to bet on.” WebAssembly keeps showing up where JavaScript naturally struggles: graphics-heavy experiences, compute-intensive features, and workloads that want near-native performance without leaving the browser. As Wasm matures, it quietly expands what “web app” can mean, turning the browser into a practical runtime for edge-case performance rather than a hard constraint teams work around. Tech jobs in 2025 quietly stabilized, even as layoffs stayed part of the story: After the brutal reset in 2022 and 2023, 2025 looked less like a rebound and more like a steady return to normal. Open roles kept inching up across startups and public tech companies, while layoffs were far less sweeping than the 2023 peak and increasingly concentrated in specific companies. The story of 2025 wasn’t a hiring frenzy, it was selective job creation and early signs that technical hiring is rebuilding momentum. JavaScript turned 30:Last year, our favorite semicolon-optional, closure-loving, eventually-async language turned 30. It’s officially old enough to rent a car, and it’s still picking up new features every year. Thirty years of function-scope confusion, a brief era where jQuery felt mandatory, and endless hours untangling asynchronous code. Through all of it, JavaScript stepped out of the browser, powered the rise of Node.js, and now runs everywhere from edge runtimes and cloud platforms to tiny devices. This week’s news corner JavaScript Rising Stars 2025 points to a new center of gravity: workflows: n8n emerged as the clear breakout winner, jumping from fifth place last year to number one with a record setting surge in GitHub stars, reflecting growing demand for AI powered automation and low code workflows. React continues to anchor the frontend world, reclaiming the top framework spot, while projects like shadcn/ui and react-bits underline the momentum around composable, design forward UI systems. On the backend, newcomer Motia signals a potential shift toward unified full-stack frameworks that collapse APIs, workflows, and AI agents into a single abstraction, making this year’s rankings less about incremental wins and more about where modern JavaScript development is heading next. Node.js shipped coordinated security releases across active lines: Node.js pushed security updates for v20.x, v22.x, v24.x, and v25.x on January 13, 2026, addressing multiple vulnerabilities across the active release lines. If you’re running Node in production, this is the kind of patch cycle you treat like a sprint task, not a “later” item. The practical move: check your runtime versions, plan a fast upgrade window, and make sure your dependency chain (especially anything touching HTTP, permissions, or request parsing) gets the same level of scrutiny. Angular 21.1 is landing, and the RC is already hinting at the direction: Angular 21.1 is expected imminently, and the 21.1.0-rc.0 release is a useful preview of what the team is tightening up next. Even if you don’t jump on minors day one, RC notes are a strong early warning system: they show what’s being stabilized, what’s being nudged into “new default” territory, and what might create friction in apps that sit a little off the happy path. Claude Code added Tool Search, making “tool use” feel less manual:Tool Search is now showing up in the Claude ecosystem, and it changes the ergonomics of agentic coding. Instead of you hard-wiring a small set of tools into context, the system can discover and select from a broader tool universe without blowing up the prompt budget. The result is less ceremony around “which tool do I expose,” and more momentum when you’re moving between code, docs, and actions. What do you want more of in 2026? Hit reply and tell us! Aaand that’s a wrap! This issue is a reset around one idea: web dev is rewarding teams that build with clearer contracts and tighter guardrails. From async control and resilient error states to TypeScript’s rise, production-ready AI workflows, and WebAssembly getting practical, the direction is the same. Even this week’s updates fit the pattern: patch Node, track Angular’s next release, and watch how tool-first coding is evolving. Before you close this tab, pick one default you’ve been relying on and tighten it. If something surprises you, hit reply and tell us what you learned. Cheers! Editor-in-chief, Kinnari Chohan Have any ideas you want to see in the next article? Hit Reply! SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
02 Feb 2026
9 min read
Save for later

WebDevPro #124: Why Modern JavaScript Feels Fragmented, Plus This Week’s Updates

Kinnari Chohan
02 Feb 2026
9 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #124 Why Modern JavaScript Feels Fragmented, Plus This Week’s Updates Crafting the Web: Tips, Tools, and Trends for Developers Webinar: How to Build Faster with AI Agents Learn how full‑stack developers boost productivity by 50% with AI agents that automate layout, styling, and component generation through RAG and LLM pipelines. See how orchestration and spec‑driven workflows keep you in control of quality and consistency. Save your seat! Welcome to this week’s issue of WebDevPro! Before we get into the updates and tools, let’s start with a familiar moment. Imagine this. You’re deep in a JavaScript codebase, following a clean map and filter chain, when you suddenly land in a class full of mutable state and lifecycle methods. Same language, different mental model. That shift is easy to miss, but over time, it adds real cognitive overhead. Modern JavaScript often feels more complicated than it needs to be, even when the syntax itself is familiar. JavaScript doesn’t neatly transition from one programming style to another as projects grow. Instead, it accumulates them. You might begin with step-by-step procedural code, adopt functional patterns along the way, and later introduce object-based structures to manage scale. In practice, most real-world codebases blend all three. In this issue, we'll start with a deep dive into JavaScript, looking at how that layering came to be, why it can make code harder to reason about, and how to approach those tradeoffs with more intention. Then we will move on to the key updates from the past week, and wrap up with a tool worth adding to your workflow. Procedural thinking as the foundation For many developers, procedural logic is the starting point. Programs are expressed as a sequence of steps executed in a clear and predictable order. Control flow is explicit, and it is easy to follow how data moves through the program. Loops and conditional statements play a central role in this style. They make execution order visible and help explain how decisions affect program behavior. for (let i = 0; i < items.length; i++) { if (items[i].active) { handle(items[i]) } } This approach emphasizes execution order and state changes. It works well when programs are small and logic is linear. Importantly, this style never disappears. Even as programs grow, procedural code remains a valid and familiar tool. Introducing functional patterns As programs become more complex, functional patterns begin to appear more frequently. Array methods and callbacks provide a more concise way to work with collections and reduce repetitive looping logic. Instead of describing how a loop executes, this style focuses on what transformation is applied to the data. items .filter(item => item.active) .forEach(handle) Here, intent is clearer, but control flow is less explicit. Readers must understand how each method behaves and how functions are applied. For some, this improves readability. For others, it introduces a new layer of abstraction that requires adjustment. Crucially, this style does not replace procedural logic. Both approaches coexist, and developers are expected to move between them as needed. Shifting toward composition As functional ideas are reinforced, composition becomes more prominent. Functions are treated as reusable building blocks that can be passed around and combined. This encourages thinking in terms of data flow rather than execution steps. Logic is expressed by chaining operations together, which can make intent clearer while obscuring the underlying sequence of events. At this stage, developers are often working with at least two distinct paradigms simultaneously: procedural control flow and functional composition. Object-based organization for larger programs As programs grow further, object-based organization emerges as a way to manage complexity. Classes and objects group related data and behavior, providing a structural model for larger systems. class ItemHandler { handle(item) { if (item.active) { // ... } } } This approach shifts the focus again. Instead of steps or transformations, code is organized around responsibilities and roles. Object-based organization can improve clarity in large programs, but it introduces a third way of thinking. Procedural, functional, and object-oriented styles now coexist within the same language and often within the same codebase. Accumulation instead of replacement A defining characteristic of JavaScript is that new paradigms are added without removing older ones. Procedural logic remains valid even as functional patterns appear. Functional composition continues to be useful when object-based structures are introduced. Nothing is deprecated at the architectural level. Every style remains available, and developers are free to choose the approach that fits the problem at hand. This cumulative nature explains why JavaScript codebases often contain a mixture of paradigms. The language supports each style equally and does not enforce a single architectural direction. Understanding the resulting tension As paradigms accumulate, readers and developers must constantly adjust how they interpret code. Different sections of a program may require different mental models depending on the style being used. This flexibility is powerful, but it increases cognitive load. Understanding a program requires not only knowledge of syntax, but also awareness of which paradigm is currently in play. Maintaining consistency becomes more challenging as programs grow. The same language constructs can support multiple architectural interpretations depending on context. Final words JavaScript is not broken. It is intentionally flexible. The challenge is not choosing the right paradigm, but navigating multiple paradigms at once. When procedural logic, functional composition, and object-based organization coexist, developers must be deliberate about how and when each style is used. Understanding how these paradigms accumulate rather than replace one another helps explain why modern JavaScript can feel complex even when the individual concepts are familiar. Want to go deeper? JavaScript from Beginner to Professional is a solid pick for developers transitioning into JavaScript. It uses more than 100 practical exercises and projects to develop real, working JavaScript skills. This Week in the News ⚙️ Node.js v25.5.0 Is Out:Node.js has shipped v25.5.0, continuing the steady pace of updates ahead of the next LTS release. This one focuses on runtime fixes and smaller improvements rather than headline features. If you like keeping an eye on where Node is heading next, this release is worth skimming through. 🧱 The Boring JavaScript Stack Reaches v1.0: The Boring JavaScript Stack has officially hit v1.0, and the name is very much intentional. It is an opinionated full-stack starter built with Sails, Inertia, Tailwind CSS, plus your choice of Vue, React, or Svelte. The goal here is fewer decisions, less setup churn, and getting to actual product code faster. 🧠 A Conversation with Anders Hejlsberg: Last week’s interview with Anders Hejlsberg offers a thoughtful look into the mindset of one of the most influential language designers behind C# and TypeScript. The discussion touches on language evolution, long-term trade-offs, and what it takes to design tools that scale with both developers and ecosystems. For an overview of the conversation, Aaron Winston summarizes Hejlsberg's lessons from C# and TypeScript on fast feedback loops, scaling software, open source visibility, and building tools that last. Beyond the Headlines 🐢 Fixing TypeScript Performance Problems:If your TypeScript project has started to feel slow, this article will feel very familiar. It walks through common causes of sluggish builds and editor lag in larger TS codebases. There are practical fixes here that teams can apply without rewriting everything from scratch. 🧭 JavaScript Frameworks Heading Into 2026: This is a community-driven look at how JavaScript frameworks are shaping up as we move toward 2026. It talks less about hype cycles and more about adoption, trade-offs, and long-term sustainability. A good read if you are thinking about whether to stick, switch, or wait with your current stack. 🅰️ Angular Community Stories and Signal Forms: The Angular team shares new community stories along with real-world code samples. There is also an update on Signal Forms, which builds on Angular’s signals model for handling reactive forms. If you work on form-heavy Angular apps, this is a useful glimpse into where things are going. 🤖 How Cursor Shipped Its Coding Agent: This post breaks down how Cursor built and shipped its AI coding agent. It goes into architectural decisions, trade-offs, and the realities of putting an AI-assisted dev tool into production. A solid behind-the-scenes read if you are curious about how modern developer tools are actually built. Tool of the Week ⚡Bun Gets Faster with v1.3.7 Bun shipped v1.3.7 last week with a JavaScriptCore update that brings noticeable performance gains, including faster async and await handling and ARM64 improvements. There’s also a new option to generate profiling data in Markdown for easier sharing, along with native support for JSON5 and JSONL. If you are curious why Bun keeps gaining traction, this article is still a good explainer on what’s driving its adoption. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
09 Feb 2026
3 min read
Save for later

WebDevPro #125: Where AI actually saves time in modern development workflows

Kinnari Chohan
09 Feb 2026
3 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #125 Where AI actually saves time in modern development workflows Crafting the Web: Tips, Tools, and Trends for Developers Hi , Welcome to this week’s issue of WebDevPro! Aditya Agarwal, Facebook’s 10th employee and former CTO of Dropbox, sparked a wider conversation in the developer community this week after sharing a personal reflection about coding with Claude, Anthropic’s AI assistant. After a weekend building with Claude, he wrote: “It was very clear that we will never ever write code by hand again.” Then he admitted something more surprising: He felt happy, but also disoriented. Now that isn’t a prediction for “someday.” It’s a workflow shift happening right now. The real challenge for most teams isn’t access to AI. It’s integration. How do you apply AI to scaffolding, debugging, refactoring, UI generation, and delivery pipelines without disrupting your stack or compromising quality? That’s why this upcoming Progress workshop stood out to us. It’s designed to help developers integrate AI into real workflows, without losing control over quality, security, or governance. Webinar: AI for Developers – How to Achieve a 50% Productivity Boost If interested, sign up here This session breaks down technical workflows, architectural considerations, and hands-on demos showing how AI can augment development tasks, without requiring framework changes or replatforming. We’ll cover topics you care about: AI‑Assisted Component Scaffolding How AI can generate baseline UI components, form logic, data-binding structures, and reduce initial setup time. Debugging & Refactoring Using AI Agents Techniques for leveraging AI to identify bottlenecks, suggest optimized patterns, and modernize legacy code. AI‑Enhanced UI Development with Kendo UI & Telerik Practical examples of generating boilerplate, bindings, CRUD screens, tests, and documentation directly from your existing tech stack. Integrating AI into Dev Workflows Where AI fits into CI/CD, code reviews, architectural planning, prototyping, and cross-team collaboration. How Teams Achieve Measurable Gains (up to 50%) Real scenarios showing reduced time spent on repetitive implementation tasks, improved code consistency, and faster iteration cycles. Security & Practical Limitations What to automate, what not to automate, and how to ensure AI‑generated code aligns with team standards. Who should attend: Senior devs building complex front‑end or full‑stack applications Dev leads and architects driving technical decision‑making Teams exploring AI adoption without changing their existing stack Register & Save Your Seat! 👉 Join the webinar and see how AI can be embedded into real engineering workflows. 📅 February 26, 2026 ⏰ 9:30 PM GMT+5:30 Secure your spot! SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
16 Feb 2026
11 min read
Save for later

WebDevPro #126: Using AI in Development Without Losing Architectural Control

Kinnari Chohan
16 Feb 2026
11 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #126 Using AI in Development Without Losing Architectural Control Crafting the Web: Tips, Tools, and Trends for Developers Webinar: How to Build Faster with AI Agents Learn how full‑stack developers boost productivity by 50% with AI agents that automate layout, styling, and component generation through RAG and LLM pipelines. See how orchestration and spec‑driven workflows keep you in control of quality and consistency. Save your seat! Welcome to this week’s issue of WebDevPro. If you’ve written any meaningful amount of code recently, AI is already part of your workflow. It finishes your lines, scaffolds components, suggests refactors, and removes a lot of the friction from day-to-day implementation. At the same time, more teams are shipping AI-powered capabilities directly into their products. The productivity gains are real. Development moves faster. Experimentation becomes easier. But speed introduces a structural question that doesn’t get talked about enough. When implementation becomes this easy, AI can start shaping architecture by default. Not through dramatic failures, but through small decisions that slip in unnoticed. A suggestion becomes a pattern. A pattern becomes the norm. Over time, structure starts drifting, responsibilities blur, and the codebase becomes harder to reason about. This piece looks at how to prevent that. We’ll look at practical ways to keep architectural control while still using AI heavily: how to set guardrails before generating code, where AI logic should live once it becomes part of your product, and what to review so “correct” output doesn’t slowly weaken your design. AI should accelerate implementation, not quietly define structure or responsibility. When Implementation Speed Becomes the Default When AI integrates into the development environment, the nature of coding changes. Developers move from writing every line manually to reviewing and selecting generated suggestions. That shift is efficient, but it also changes what drives consistency in a codebase. Suggestions are generated from generalized patterns. They can be correct, but correctness is not the same as architectural alignment. A project’s structure depends on decisions that hold across features, not on what looks plausible inside a single file. Encode Guardrails Before You Generate One practical way to preserve architectural control is to define expectations before generation. In practice, this means writing down the constraints that matter in your codebase so the assistant consistently reinforces them. For example, a strong instruction set begins by defining the development role clearly: You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices. From there, it becomes easier to add concrete constraints that prevent structural drift. Even a small rule can make a difference, such as discouraging weak typing: Avoid the any type; use unknown when type is uncertain This kind of guidance does not guarantee perfect output. It does something more important: it keeps the assistant working within a defined standard instead of inventing one through suggestion. When AI Becomes Part of the Application The architectural stakes rise when AI is not only assisting development but also powering feature behavior. At that point, the questions are no longer about scaffolding. They become questions of placement, responsibility, and cohesion. One useful pattern is to configure AI behavior deliberately through a system instruction and then route all interaction through a structured interface. Example model configuration and system instruction: const instructions = ` Welcome to citypass. You are a superstar agent for this car parking validator. You will assist users by submitting parking tickets. You can convert date phrases to ISO strings and act as a geocode service to convert a location or address to coordinates long/lat. `; const ai = getAI(firebaseApp); const model = getGenerativeModel(ai, { model: 'gemini-2.5-flash', systemInstruction: instructions, tools: [toolset] }); this.chat = model.startChat(); This approach makes two architectural decisions explicit. First, AI behavior is constrained by a clear instruction. Second, interaction happens through a controlled interface instead of being scattered across unrelated parts of the application. A Practical Boundary: UI for Input, Services for Processing When AI is exposed to users, architectural control often comes down to a simple boundary. The UI collects input and shows results. The AI processing stays encapsulated behind an interface that can be tested and evolved. A clean example is offering an “AI Creator” entry point near an existing workflow, opening a modal to capture a prompt: Ticket creation flow with an AI Creator entry point used to capture user input for AI-assisted ticket creation. AI Creator button and modal markup: <button nz-button nzType="link" type="button" (click)="isVisible.set(true)" >AI Creator </button> <nz-modal [(nzVisible)]="isVisible" nzTitle="AI Creator" (nzOnCancel)="isVisible.set(false)" (nzOnOk)="ok()"> <ng-container *nzModalContent> <textarea [(ngModel)]="prompt" rows="3" placeholder="Enter ticket details" nz-input> </textarea> </ng-container> </nz-modal> This interface design matters because responsibility stays clear. The component manages interaction and user input. The AI-driven work happens only when the user explicitly triggers it, and the application can decide where that processing belongs. The Drift Risk: Structure by Convenience AI rarely causes dramatic architectural failures. The more common risk is incremental drift. A generated approach is accepted, then repeated, and then becomes the default. Over time, similar features are built with slightly different patterns, responsibilities move across layers, and ownership becomes less obvious. This is why the guiding principle needs to be visible early in the workflow: AI should accelerate implementation, not make architectural decisions. Maintaining Architectural Control Architectural control in an AI-assisted workflow comes from a small set of repeatable practices: Define expectations before generation so the assistant reinforces standards Encapsulate AI-enabled behavior behind clear interfaces Review generated code for placement and responsibility, not only for correctness These practices keep speed from rewriting structure. Final words AI can absolutely increase velocity. It can also unlock product capabilities that would have been expensive or slow to build even a year ago. But long-term maintainability still comes down to structure. The difference is that AI changes how structure gets created. It can reinforce good decisions, or it can quietly introduce new patterns through convenience, repetition, and speed. That’s why boundaries matter. When they’re clear, AI becomes a multiplier for discipline. When they’re loose, drift becomes the default. AI should accelerate implementation, not quietly define structure or responsibility. Architectural control isn’t about resisting AI. It’s about staying deliberate while moving fast. Want to learn more? Get your copy of Angular Projects, 4th Edition now! This Week in the News 🧩TypeScript 6.0 beta and roadmap shift: TypeScript 6.0 beta introduces incremental improvements across type checking and configuration while laying groundwork for the long-discussed native rewrite in Go that promises serious performance gains. The team is clearly thinking beyond syntax tweaks and focusing on compiler architecture, which signals that large codebases and build speed are now central to the TypeScript story. For teams pushing monorepos or heavy CI pipelines, this roadmap matters more than any single feature flag. 📊 State of JavaScript 2025 survey results: The State of JavaScript 2025 survey provides a broad snapshot of how developers are working across the JS ecosystem. TypeScript continues to see strong adoption, reinforcing its position as a standard part of modern JavaScript development. In tooling, newer build systems and faster runtimes are gaining momentum, reflecting a clear preference for performance-focused developer experience. The survey also compares usage, satisfaction, and interest across frameworks, testing tools, browser APIs, and runtimes, highlighting which technologies feel stable and which are generating curiosity. Overall, the results show an ecosystem that is maturing around modern defaults while steadily evolving toward faster tooling and more streamlined workflows. ⚛️ State of React 2025: The State of React 2025 survey offers a data-backed look at how developers are building with React today. Core tooling remains strong: Next.js, React Router, and Redux continue to see widespread usage, while TanStack Query and Axios are common choices for data handling. On the build side, Vite has emerged as the dominant tool, reflecting the ecosystem’s shift toward faster development workflows. UI libraries like MUI and shadcn/ui show meaningful adoption, and modern state solutions such as Redux Toolkit are preferred when external state is needed. The results also highlight a practical trend: many teams still rely primarily on built-in React APIs for state management, reaching for external libraries only as complexity grows. Beyond the Headlines ⚡Running Next.js at Enterprise Scale:Next.js scales well, but enterprise usage needs more than default settings. This piece breaks down what changes when you’re running Next.js under real traffic: defining clear SLOs/SLAs, instrumenting performance monitoring, and understanding the framework’s request lifecycle so you can spot bottlenecks early. It also highlights practical architecture choices like using CDNs and caching effectively, picking the right rendering strategy (SSG vs SSR) per route, and planning for horizontal scaling with shared caches and load balancing. 🐢 Is Claude Code losing its engineering edge:This post critiques recent shifts in Claude Code’s behavior, arguing that guardrails and simplifications are reducing its usefulness for experienced developers who value precision and depth. It reflects a broader tension in AI tooling between safety, accessibility, and raw capability. For developers relying on AI as a serious engineering assistant, subtle model behavior changes can have an outsized impact on trust and workflow. 🧭 Debugging workflows that actually benefit from AI: An exploration of AI-assisted debugging workflows that break down how you can leverage models to trace issues, interpret stack traces, and accelerate root-cause analysis without feeling like you’re guessing at prompts. ⚛️ React Server Components performance insights: This piece examines how React Server Components behave in real world performance scenarios, moving past high level explanations to measure tradeoffs in rendering, hydration, and network boundaries. It challenges simplistic assumptions about automatic speed gains and highlights where architectural decisions still matter. For teams evaluating RSC adoption, the nuance here is critical before refactoring large parts of an app around the paradigm. Tool of the Week 🌀🛠️ OpenClaw — The AI Agent That Does Work for You OpenClaw is an open-source autonomous AI agent that goes beyond chat: it can execute real tasks like managing email, calendars, messaging apps, and other workflows by integrating with platforms such as WhatsApp, Telegram, and Slack. Developed by Austrian engineer Peter Steinberger, it grew from an experimental project into one of the fastest-adopted agent tools in the community because of its ability to act instead of just respond. Since its introduction in November, OpenClaw has seen a viral rise, surpassing 100,000 stars on GitHub and attracting 2 million visitors in a single week, signaling strong developer curiosity around agent-based systems. This week, OpenClaw made headlines when creator Peter Steinberger announced he’s joining OpenAI. Sam Altman highlighted Steinberger’s expertise in multi-agent systems as strategically important for building the next generation of personal AI assistants, and OpenClaw will continue as an open-source foundation supported by OpenAI. This move signals how quickly agent-centric tooling is becoming a central focus for major AI platforms. For anyone interested in trying OpenClaw yourself or seeing it in action, this tutorial walks through setup and use:📺 OpenClaw Full Tutorial for Beginners. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
23 Feb 2026
12 min read
Save for later

WebDevPro #127: Design Patterns as Decision Frameworks, Not Recipes

Kinnari Chohan
23 Feb 2026
12 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #127 Design Patterns as Decision Frameworks, Not Recipes Crafting the Web: Tips, Tools, and Trends for Developers Unblocked: The context layer your AI tools are missing Give your agents the understanding they need to generate reliable code, reviews, and answers. Unblocked builds context from your team’s code, PR history, conversations, documentation, planning tools, and runtime signals. It surfaces the insights that matter so AI outputs reflect how your system actually works. See how it works Welcome to this week’s issue of WebDevPro. If you’ve been building and maintaining systems for a while, you’ve probably felt the friction that doesn’t show up in code reviews. And here’s something that doesn’t get said often enough: most engineering pain isn’t caused by bad code. It’s caused by unexamined decisions. Nobody deliberately optimizes for rigidity. Nobody sets out to make performance worse or future changes harder. But when trade-offs aren’t made explicit, that’s exactly where teams end up. You notice it when: A small requirement change forces a wide refactor A performance issue appears under load and the root cause isn’t obvious “Best practice” debates stall because everyone is arguing from instinct, not intent We’re often handed rules like these: Use strict equality Avoid blocking I/O Prefer Map over Object Use Set for deduplication Be careful with parseInt These guidelines are useful, especially early on. They prevent common mistakes. But over time, following rules without understanding the trade-offs turns into habit, not judgment. This week’s issue looks at patterns differently. Not as templates to apply or badges of clean code, but as decision frameworks. By the end of this article, you’ll start seeing what a piece of code is really optimizing for, notice the hidden costs behind everyday implementation choices, think more deliberately about where change should happen, and reframe conversations from “Is this correct?” to “What are we trading off?” That shift from applying rules to making deliberate trade-offs is where engineering starts to feel intentional. And that’s what we’re unpacking today. Code Shape and the Cost of Change Most problematic code works. That’s why it survives. createUser(name, email, password, phoneNumber, role = 'guest') There’s nothing obviously wrong here. Until the domain changes. Add a required field. Deprecate one parameter. Split roles into multiple variants. Now you’re updating dozens of call sites and hoping argument order wasn’t misused somewhere. Switching to object parameters: createUser({ name, email, password, phoneNumber, role = 'guest' }) This changes what the API optimizes for. Specifically, order stops mattering, adding fields becomes safer, and call sites become self-documenting. This is design thinking at the function level. You’re choosing a shape that absorbs change instead of amplifying it. Hidden Transformations and Predictability Loose equality in JavaScript produces results that surprise people: false == 0 // true '' == false // true [] == false // true These aren’t bugs; they follow the language’s coercion rules. The issue is that the conversion happens implicitly. When a comparison behaves unexpectedly, it looks like a logic error, not a type conversion chain. Using === keeps comparisons explicit. It removes hidden work. Parsing has similar edges. parseInt(0.0000005) // 5 parseInt converts its input to a string, then parses from the start until it hits an invalid character. A number like 0.0000005 may be represented as "5e-7", so parsing stops at "e". This is documented behavior. The problem isn’t the function. It’s assuming there are no intermediate transformations. The broader lesson: implicit conversions reduce clarity. When behavior depends on hidden steps, bugs become harder to reason about. Runtime Reality: Blocking Work and Delegation Node.js executes JavaScript on a single main thread that drives the event loop. CPU-heavy synchronous code blocks that thread. While it runs, nothing else progresses. That’s why synchronous work in request handlers can reduce throughput and increase latency under load. Asynchronous APIs often help because some operations can be delegated to the system or to worker threads (for example, many crypto operations have asynchronous variants). The main thread remains free to coordinate. But async alone doesn’t solve architectural problems. Performance often improves more by reducing how often expensive work runs than by simply changing it to async. Caching, batching, and eliminating redundant computation frequently have a bigger impact than switching APIs. The real pattern isn’t “async good, sync bad.” It’s understanding where your runtime is serial and designing accordingly. Data Structures as Cost Models Choosing between Object, Map, Array, and Set is about workload and not preference. Objects fit well when: keys are known and stable you’re modeling record-like data JSON serialization matters Maps fit well when: keys are dynamic frequent insertions and deletions occur iteration order must be preserved non-string keys are required Arrays excel at ordered data and transformations. Sets excel at uniqueness and fast membership checks. No structure is universally better. Each implies a different cost model. When you align structure with usage patterns, the code becomes simpler and more predictable. Classic Patterns: Controlling Where Change Happens Design patterns get introduced as diagrams. Boxes, arrows, interfaces. In practice, nobody reaches for Strategy because they love abstractions. They reach for it because some piece of logic keeps changing and it’s starting to infect everything around it. Pricing rules change. Validation rules change. Feature flags change. If that logic is scattered across the system, every update becomes a small cleanup project. Strategy just gives that volatility a place to live. Observer shows up when components are getting too aware of each other. One module updates something and suddenly three others need to react. Hard-coded calls turn into a web of dependencies. Events or observers aren’t about elegance. They’re about reducing how much each part needs to know. Adapter is what you write when an external API doesn’t quite fit your model, and you don’t want your entire codebase shaped by someone else’s decisions. It’s a boundary. A buffer. None of these patterns are impressive on their own. What they’re really doing is controlling where change hurts. They add indirection, yes. That indirection costs something. But in return, you stop paying interest every time requirements shift. That’s the trade. Containment Over Cleverness There’s always a moment when modifying a prototype feels like a clever shortcut. You add a helper to Array.prototype so you don’t have to import a utility everywhere. You tweak Object.prototype to normalize something globally. It works. It even feels elegant. The catch is that you’ve quietly changed the rules for the entire runtime. Now every array in the system has that extra method. Every object inherits the behavior whether it asked for it or not. If something breaks, the failure won’t point back to your “nice little improvement.” It’ll surface somewhere unrelated, and debugging it will take longer than the shortcut saved. In small scripts, this might be fine. In shared codebases, libraries, or long-lived services, it’s a different story. The blast radius is bigger than it looks. Most engineers grow out of this not because someone tells them it’s wrong, but because they’ve spent a day tracing a bug back to a global modification they forgot existed. After that, containment starts to feel a lot more attractive than cleverness. Final words: Making Trade-offs Explicit The deepest anti-pattern isn’t picking the “wrong” API but rather building for neatness today in places that are guaranteed to change later. That’s why patterns matter. Not because they’re clever, but because they force the trade-offs into the open: CPU vs latency memory vs speed coupling vs flexibility simplicity now vs adaptability later Used like recipes, patterns turn rigid fast. Used like decision frameworks, they do something much more valuable: they make teams talk about consequences instead of preferences. You spend less time arguing about “best practices,” and more time agreeing on what kind of system you’re actually building. That’s the shift from following rules to developing engineering judgment. This Week in the News 🧩TypeScript 6.0 Beta Is Here and 7.0 Will Change More Than You Think: TypeScript 6.0 is now in beta and while it looks like a transitional release, it lays the groundwork for the Go-based TypeScript 7. Some changes may subtly affect existing projects, especially around deprecated behaviors and internal shifts. This is the release to test against early so you are not caught off guard when 7.0 lands with deeper architectural changes. 🌐 WebMCP and EPP Show Where Chrome Wants the Web to Go Next: With WebMCP and EPP, Chrome is exploring structured communication between web apps and external processes. This could reshape how developer tools, AI agents, and browser-based apps coordinate capabilities. If you care about automation-heavy workflows or AI-assisted development, this signals where the platform is heading. 🔗 npm Just Made Supply Chain Security Harder to Ignore: npm 11.10.0 rolls out bulk trusted publishing configuration and stronger lifecycle script protections. Teams managing multiple packages can now streamline trusted publishing, while script security improvements reduce risk from malicious or unexpected install-time behavior. Worth reading if you maintain libraries or ship production packages regularly. 🛠️ Interop 2026 Aims to Remove More Cross Browser Headaches: Interop is a yearly cross-browser effort where engine teams align on specific platform features and work together to close gaps in behavior and support. Interop 2026 is now announced, continuing the collaboration between WebKit, Chromium, and Gecko with a fresh set of focus areas. For developers, it is one of the most reliable signals of which web platform fixes are actually likely to land across all major browsers. Beyond the Headlines ⚡Node vs Deno vs Bun Benchmarks Show Where the Real Gaps Are: Fresh performance benchmarks compare Node.js, Deno, and Bun across real-world scenarios rather than synthetic micro tests. The results challenge a few common assumptions about which runtime is “fastest” and where that speed actually matters. Worth reading before you make runtime decisions based purely on hype or benchmark screenshots. 🧠 Performance Is Not a Technical Problem: This essay argues that most performance issues stem from priorities, incentives, and product decisions rather than raw engineering limits. It reframes optimization as a cultural and organizational question, not just a profiling exercise. A sharp perspective shift for anyone working on growing codebases where “we’ll fix it later” keeps winning. ♟️What Deep Blue Still Teaches Us About AI Hype: Simon Willison revisits IBM’s Deep Blue moment and connects it to today’s AI narratives. The piece explores how milestone victories shape public perception, funding, and expectations long after the headlines fade. A thoughtful read for developers navigating the current AI wave and trying to separate signal from spectacle. 🧵 HTTP in Node.js Gets Tricky Fast and This Guide Explains Why: Making an HTTP request in Node.js looks simple until you hit retries, timeouts, streaming, backpressure, and error handling in real production code. This deep dive breaks down the patterns that help you move beyond quick fixes and build request logic that stays reliable under load. It’s also a great preview of the thinking behind Node.js Design Patterns, one of the most practical books for developers building serious Node.js systems. Tool of the Week 🧮 Excelize Makes Spreadsheet Automation in Go Feel Like a Superpower If you’ve ever had to generate Excel reports, clean up spreadsheets, or build export pipelines, you already know the pain: most tooling is either fragile or wildly overcomplicated. Excelize, built by Ri Xuis an open-source Go library that makes working with real .xlsx files feel straightforward, from formatting and formulas to charts and large-file handling. The most interesting part is the story behind it. The write-up on how Excelize was builtshows what it takes to make “boring” infrastructure reliable: handling OpenXML edge cases, keeping the API Go-friendly, and evolving features without breaking existing users. It’s a strong example of how long-term OSS gets built, one painful spreadsheet at a time. Good developer tooling rarely starts as “let’s build a library.” It starts as “this keeps hurting, let’s fix it properly.” Excelize is the result of sticking with that fix long enough to turn it into real infrastructure. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan Brought to you in cooperation with Unblocked: SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
02 Mar 2026
15 min read
Save for later

WebDevPro #128: TypeScript Under Pressure in Evolving Full Stack Systems

Kinnari Chohan
02 Mar 2026
15 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #128 TypeScript Under Pressure in Evolving Full Stack Systems Crafting the Web: Tips, Tools, and Trends for Developers Most Spring Boot projects stop at REST APIs Real systems require service discovery, API gateways, centralized configuration, and built-in resilience. In this live, hands-on workshop, you’ll build a working microservices system end-to-end, define service boundaries, wire up discovery, configure a gateway, and handle failures properly. 🎟 Register now and get 40% off with code SAVE40 Welcome to this week’s issue of WebDevPro. This issue looks at a common transition in modern engineering: taking a fast proof of concept and turning it into something that can survive real change. Consider a small internal AI support chatbot. The architecture is simple. A React frontend collects messages and sends them to an Express POST /api/chat endpoint. The server returns a short reply. The goal here is speed, not durability. To move quickly, the frontend stores messages as any[]. The backend reads req.body.messages from untyped JSON and returns a response like: { "reply": "You said: hello" } There is no formal contract between frontend and backend. The agreement lives in shared understanding: reply is a string, and messages is an array of objects with role and content. When both sides are written close together, this feels stable. Then the requirements change. The reply must now include structured data: the assistant’s message, references, and follow-up questions. The backend evolves and begins returning an object under reply instead of a string. The frontend does not change. The build passes. The browser fails at runtime because React attempts to render an object as if it were a string. Nothing in the type system flagged the mismatch because nothing at the boundary defined what the response was supposed to be. This is where TypeScript either acts as a safety mechanism under change or becomes a thin layer of annotations. What follows is a practical look at how that same chatbot evolves into a production-safe system by making contracts explicit and boundaries deliberate. Phase One: Turning Assumptions into a Shared Contract The chatbot did not break because the change was complex. It broke because the change was invisible to the type system. On the frontend, state was typed as any[]. The API response was treated as whatever res.json() returned. On the backend, req.body was used without an explicit shape. From TypeScript’s perspective, there was nothing concrete to compare, so the change in response structure did not register as a problem. The fix was not adding defensive conditionals in the UI. It was introducing a shared domain contract and making both sides depend on it. Instead of allowing the frontend and backend to “just agree,” the system defines explicit types for: ChatMessage ChatRequest ChatReply ChatResponse These types live in a shared module that both the frontend and backend import. The frontend state becomes ChatMessage[]. The API call returns Promise<ChatResponse>. The backend handler constructs a value that must satisfy ChatResponse. At that point, the contract stops being an assumption and becomes code. If someone changes the shape of reply again, the compiler will force every dependent piece of code to reconcile with that change. The mismatch that previously appeared in the browser now appears during build. In practical terms, this shifts the feedback loop. Instead of discovering drift during manual testing or after deployment, the team discovers it the moment they try to compile. That difference seems small, but over time it fundamentally changes how safely a system can evolve. Phase Two: Making the Network Boundary Honest Introducing shared types stabilizes collaboration between frontend and backend. It does not solve everything. There is still one place where untyped data enters the system: the network boundary. On the backend, req.body arrives as raw JSON. On the frontend, res.json() returns data that TypeScript cannot inspect at runtime. Even with shared contracts, the system is still trusting external input. In the chatbot’s case, this matters more as the system grows. Deployments may not always be perfectly synchronized. A proxy or middleware layer might alter payloads. A partial rollout could temporarily mix versions of frontend and backend. Shared types alone cannot guard against that. The practical adjustment is simple but important: treat boundary data as unknown until it is validated. Instead of assuming that req.body matches ChatRequest, the backend checks that it actually does. Instead of assuming that res.json() returns a valid ChatResponse, the frontend verifies the shape before proceeding. These checks do not need to be elaborate. Lightweight type guards are enough to confirm that required properties exist and have the correct types. Once the data passes that validation step, the rest of the system can rely on strong typing with confidence. This introduces a clear separation of responsibilities: Outside the boundary, data is untrusted. Inside the boundary, data is guaranteed to match the domain contract. In day to day development, this prevents subtle corruption. Rather than allowing malformed data to move deeper into the UI or business logic, the system fails immediately at the edge. Errors become explicit and local instead of diffused and harder to trace. The chatbot remains small, but its architecture becomes more deliberate. The boundary is no longer a blind spot; it becomes a controlled entry point. Phase Three: Containing Vendor Volatility As the chatbot matures, another requirement arrives: support more than one LLM provider. This is a common inflection point. Early on, it is convenient to wire the backend directly to a single SDK. The response from the provider flows straight through the server and into the UI. It feels efficient. It also quietly couples your entire stack to a vendor’s response shape. If that vendor changes its format, or if you decide to introduce a second provider, the ripple effects can reach the frontend quickly. What began as a backend integration detail becomes a full-stack coordination problem. In the chatbot’s evolution, this risk is handled differently. Instead of exposing raw provider responses, the system defines a provider interface that returns a domain-level ChatReply. Each provider implementation adapts its own SDK response into that shared shape. The rest of the application does not know or care which provider generated the reply. It only understands the domain contract. This decision seems architectural, but it has very practical consequences. Switching providers or introducing a second one no longer forces a redesign of shared types. The volatility is contained. The surface area of change is smaller. TypeScript reinforces this separation. The compiler ensures that every provider implementation produces a valid ChatReply. The backend handler depends on the interface, not on vendor-specific JSON. In a growing system, this is what stability looks like. The parts that are likely to change are isolated behind clear contracts. The parts that need to remain steady, especially the frontend, are shielded from that churn. Phase Four: Making States Explicit as Features Expand Feature growth rarely stops at integration. The chatbot evolves again. Sometimes a response includes citations. Sometimes it does not. A quick solution would be to add optional fields. Over time, optional fields accumulate. The type becomes flexible, but also ambiguous. It becomes unclear which combinations are valid and which are accidental. Instead, the chatbot models these variations explicitly using a discriminated union. A response is either a plain answer or an answer with citations, and the kind field identifies which one it is. On the frontend, rendering logic switches on that kind. The exhaustive check ensures that every variant is handled. This design choice has a subtle but important effect. When a new response variant is introduced later, the compiler highlights every place that must adapt. Nothing slips through unnoticed. In everyday development, this reduces the risk of partial updates. The type system becomes a guide for refactoring, not just a static annotation layer. The chatbot still feels small. The difference is that its possible states are no longer implied. They are declared. Phase Five: Removing Quiet Type Erosion As the chatbot grows, new endpoints appear. Health checks. Admin actions. Maybe analytics or feedback capture. The API surface expands gradually. This is usually where small shortcuts start accumulating. A common one looks harmless: const data = (await res.json()) as ChatResponse; The cast makes the compiler quiet. It also shifts responsibility back to the developer. At the exact point where the system is most exposed to incorrect data, you are asserting that everything is fine. In a small codebase, this feels manageable. In a growing one, these assertions multiply. Over time, they erode the safety you thought TypeScript was providing. In the chatbot’s evolution, this problem is addressed structurally rather than procedurally. Instead of scattering casts, the system defines a mapping between route literals and their request and response types. The endpoint determines the shape. The generic client enforces it. Now the type of /api/chat is tied directly to ChatRequest and ChatResponse. You cannot accidentally call the wrong endpoint and pretend it returns something else. The compiler resolves the relationship based on the route itself. This removes a category of silent drift. It also makes the API surface self-documenting. When someone adds a new endpoint, they define its contract in one place, and the rest of the system aligns automatically. It is a small shift in structure, but it prevents gradual type erosion. What This Evolution Actually Achieved The chatbot still answers questions. The UI still renders messages. The architecture is not radically different from the initial proof of concept. What changed is where mistakes surface. At first, mismatches appeared in the browser. After shared contracts, they appeared during compilation. After boundary validation, they appeared at the edge of the system. After isolating providers, vendor changes stopped leaking across layers. After modeling variants explicitly, feature growth became safer. After tightening endpoint typing, unsafe assumptions stopped spreading quietly. None of these changes required advanced language features. They required clarity about boundaries and discipline about contracts. In a system that evolves under real-world pressure, assumptions are the most fragile dependency. TypeScript is most valuable when it turns those assumptions into enforceable structure. The chatbot did not become more sophisticated for its own sake. It became more predictable under change. For a deeper exploration, pre-order Clean Code with TypeScript. The book takes a detailed look at the evolving chatbot system and walks through the architectural decisions, trade-offs, and production considerations behind it. If you’re a PacktPub subscriber, you can access the Early Access version right away. This Week in the News ☁️ Cloudflare rebuilds Next.js on Vite: Cloudflare unveiled vinext, an experimental reimplementation of the Next.js API surface built on Vite, reportedly put together in a week using AI coding agents and the official Next.js test suite as a spec. Instead of adapting Next’s build output for non-Vercel platforms, Cloudflare rebuilt the framework layer itself to make deployment on its infrastructure more natural. vinext already supports routing, SSR, React Server Components, server actions, middleware, and both routing systems, passing about 94 percent of the Next 16 test suite. Cloudflare claims faster builds and smaller client bundles in early benchmarks, while calling the results directional. It is still early, but this is a bold signal in the ongoing platform versus framework conversation. 🅰️ Angular Skills for coding agents: Angular developers can now equip their coding agents with “Angular Skills,” a set of curated defaults and patterns designed to guide agents toward modern Angular best practices. The project packages conventions, structure, and tooling preferences so AI-assisted workflows generate code that feels aligned with today’s Angular ecosystem. Do agent skills materially improve output quality? That remains to be seen. At the very least, they formalize standards and make human intent more explicit, which may be half the battle in AI-assisted development. 🟢Node.js 24.14.0 LTS and 25.7.0 Current released: Node shipped both an LTS and a Current release this week, and this one is more than routine version churn. The LTS update tightens up async_hooks, improves fs.watch reliability, adds proxy configuration support, and begins exposing early ESM embedder API enhancements. If you’re running backend services, SSR stacks, or dev tooling that leans on Node internals, these changes are practical, not cosmetic. Meanwhile, 25.7.0 Current gives a preview of where the runtime is heading. Framework maintainers and teams with native modules should treat this as a signal to test early and avoid CI surprises later. 📈 OpenClaw briefly overtakes Python projects on GitHub: A retro game reimplementation project saw a spike in GitHub activity that temporarily outranked major Python repositories. While largely a trend metric, this serves as a reminder that GitHub stars and ranking spikes do not always reflect lasting ecosystem impact. For developers, this is a useful gut check. Popularity metrics can highlight energy, but they don’t always indicate production relevance or long-term impact. 🤖 Claude Code turns one as distillation dispute surfaces:Anthropic marked the first anniversary of Claude Code while raising concerns about competitors using Claude outputs to train rival models through large-scale distillation. The discussion moves beyond company rivalry and into deeper questions around model output ownership, competitive boundaries, and how AI tooling companies protect their work. For developers building AI-powered products, this matters. The ecosystem is still defining what is acceptable reuse, what is extraction, and where legal lines will be drawn. Tooling decisions today are increasingly shaped by these policy and governance shifts 🏛️ React moves to the Linux Foundation:React and React Native are now governed by a newly formed React Foundation under the Linux Foundation. This shifts stewardship from a single corporate sponsor to neutral, open governance. For teams betting their front end architecture on React, this reduces long-term platform risk. Governance stability may not feel urgent in daily development, but it quietly shapes the future of roadmaps, community trust, and ecosystem continuity. Beyond the Headlines 🦀 Ladybird adopts Rust for new components: The Ladybird browser project is moving new development to Rust, citing safety and maintainability. This isn’t just about language preference. It reflects the industry’s steady migration toward memory-safe systems programming in infrastructure-level code. The browser space has historically been C and C++. Rust’s continued expansion here signals that safety is becoming a baseline expectation, not a luxury. 🤖 The five stages of AI agents: A useful framework is emerging around how AI agents evolve: from basic task automation to more autonomous, goal-oriented systems. For developers experimenting with AI workflows, this gives structure to what can otherwise feel like hype. The takeaway is simple. Not every workflow needs autonomy. Understanding where your system sits on that spectrum prevents overengineering and keeps expectations grounded. 🧠 Mitchell Hashimoto on AI adoption: Mitchell Hashimoto, the co-founder of HashiCorp shared a candid look at integrating AI tools into his workflow. Not evangelism. Not backlash. Just a practical breakdown of where AI speeds things up and where it still falls short. That grounded middle space is where most developers are operating right now. AI is useful. It is not magic. And thoughtful integration beats blanket adoption. Refactoring production systems is risky, especially when API changes and hidden coupling are involved. In this Deep Engineering session, learn how to safely evolve real-world codebases using ast-grep and Claude Code, with practical guardrails you can apply immediately. Use code WDP40 to get 40% off your seat. Tool of the Week 🛡️ Zod Makes Runtime Validation Feel Native to TypeScript If you’ve ever trusted req.body a little too much or written as SomeType just to quiet the compiler, you already know the pain: TypeScript disappears at runtime. The type system keeps you safe inside your codebase, but JSON at the network boundary remains unchecked. Zod solves that gap cleanly. It’s a TypeScript-first schema validation library that lets you define runtime validation and static types in one place. You describe the shape once, validate incoming data against it, and automatically infer the TypeScript type. No duplication. No drift between interface and validator. The real value shows up under change. When your API evolves or a provider shifts its response format, Zod forces the mismatch to surface immediately at the boundary instead of leaking deeper into your system. It turns assumptions into enforced contracts. Good architecture is often about making edges explicit. Zod gives those edges structure. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
Kinnari Chohan
09 Mar 2026
12 min read
Save for later

WebDevPro #129: Why Single Page Applications Changed How We Build Web Apps

Kinnari Chohan
09 Mar 2026
12 min read
Crafting the Web: Tips, Tools, and Trends for DevelopersAdvertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }WebDevPro #129Why Single Page Applications Changed How We Build Web AppsCrafting the Web: Tips, Tools, and Trends for DevelopersMost Spring Boot projects stop at REST APIsReal systems require service discovery, API gateways, centralized configuration, and built-in resilience. In this live, hands-on workshop, you’ll build a working microservices system end-to-end, define service boundaries, wire up discovery, configure a gateway, and handle failures properly.🎟 Register now and get 40% off with code SAVE40Welcome to this week’s issue of WebDevPro!Take a moment to think about the web apps you use every day. A dashboard updates without refreshing the page. A chat window receives messages in real time. A project board moves tasks across columns instantly.Modern web applications behave very differently from traditional websites. Interfaces update instantly, dashboards refresh without reloading the page, and navigation feels closer to using installed software than browsing documents. This experience is powered by an architectural shift called the Single Page Application, or SPA.By the end of this article, you will understand three things that shape modern frontend systems. First, what actually defines a Single Page Application beyond the buzzword. Second, why the SPA model improves responsiveness and interactivity in web applications. And third, how React enables this architecture through its rendering model and state-driven design.This is not a tutorial about setting up React or writing components. Instead, the goal is to understand the architectural thinking behind SPAs and how React fits into that model.The architectural shift behind modern web appsEarly websites were built around a page-based model. Each interaction triggered a request to the server, which generated a new HTML document and returned it to the browser. This worked well when the web primarily delivered content.Modern web applications demand a different experience. Consider tools such as analytics dashboards, project management systems, or collaborative editors. Users interact continuously with the interface. Data updates frequently, UI elements move around the screen, and navigation happens rapidly.In a traditional page-driven architecture, each of those interactions would require a server request and a full page refresh. Even small updates would rebuild the entire interface.Single Page Applications address this limitation by shifting the responsibility for rendering the interface into the browser itself.Instead of repeatedly requesting new pages, the browser loads the application once and then updates the interface dynamically as data changes. This change might sound simple, but it transforms the browser from a document viewer into a runtime environment for applications.What actually defines a Single Page ApplicationThe term “Single Page Application” can be misleading. It does not mean an application literally has only one page in the user experience. Instead, it describes how the application is delivered and rendered.A SPA typically loads a single HTML document that acts as a container for the application. Once the application initializes, JavaScript takes responsibility for rendering and updating the interface.From that point onward, most user interactions modify the current interface rather than loading new documents.Several architectural patterns define this approach.Client-side renderingIn a traditional website, the server generates HTML for each page request. In a SPA, the browser renders most of the interface.The server still plays an important role. It provides data through APIs and delivers the initial application bundle. However, the frontend application determines how that data appears on the screen.This allows the interface to update instantly when new data arrives.Persistent application runtimeBecause the application remains loaded in the browser, its logic persists across user interactions.Instead of rebuilding the interface from scratch, the system modifies the existing UI based on changes in application state.This persistence allows applications to maintain context and update small pieces of the interface without resetting the entire page.Virtual navigationUsers still see URLs change when navigating through a SPA, but the navigation logic is handled inside the application rather than by the server.The application interprets the URL and determines which components to display. From the user's perspective, the experience feels like normal navigation. Internally, the browser remains on the same underlying document.Together, these characteristics create the foundation of SPA architecture.Why the SPA model improves responsivenessThe key advantage of SPAs is not simply that they avoid page reloads. The deeper benefit is that they reduce unnecessary work.In a page-based system, the browser discards the entire interface whenever a new page loads. The server reconstructs the page, sends it back, and the browser renders it again.In a SPA, the application updates only the parts of the interface that change.Once the application code is loaded, the browser already contains everything needed to update the UI. When data changes, the system redraws only the relevant components rather than rebuilding the entire page.This approach becomes particularly valuable in highly interactive environments.Imagine a data dashboard where metrics update continuously while users filter results, adjust settings, and move between views. Rebuilding the entire interface for each interaction would create unnecessary delays.SPAs allow these updates to happen immediately because the rendering logic runs locally in the browser.However, this architecture introduces new complexity. The browser must now manage application state, UI rendering, and navigation. Without structured tools, this quickly becomes difficult to maintain.This challenge is where frameworks such as React play an important role.How React enables SPA architectureReact does not directly implement the SPA model. Instead, it provides a system that makes managing dynamic interfaces significantly easier.At its core, React helps developers answer a difficult question: how should the interface update when application data changes?Instead of manipulating the browser DOM directly, React introduces a model where the UI is described as a function of application state.This approach rests on two key ideas: the Virtual DOM and state-driven rendering.The Virtual DOM and efficient UI updatesEvery webpage contains a structure known as the Document Object Model, or DOM. The DOM represents the hierarchy of elements that make up the interface.Updating the DOM directly can become expensive when applications contain large numbers of elements or frequent updates.React addresses this problem through the Virtual DOM.The Virtual DOM is an in-memory representation of the interface maintained by React. When application state changes, React creates a new representation of the interface and compares it with the previous version.This comparison process determines exactly what has changed between the two states.React then updates only those elements in the real DOM that need to change. This process is often referred to as reconciliation.By reducing the number of direct DOM operations, React helps maintain performance even when applications grow large and complex.State as the driver of the interfaceThe second important idea in React is that the interface should be driven by state.React applications are built from components, each representing a piece of the user interface. Components maintain state that describes the data they display.When that state changes, React automatically updates the component's output and reconciles the differences in the DOM.A simplified example illustrates the concept.import { useState } from "react";function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count is {count} </button> );}In this example, the interface is directly derived from the value of count. When the state changes, React recalculates the component output and updates the interface accordingly.Developers do not manually manipulate DOM elements. Instead, they describe how the interface should look given a particular state.This declarative model simplifies reasoning about complex interfaces. It also aligns naturally with the needs of Single Page Applications, where the interface must update frequently in response to data changes.Why React became central to modern frontend developmentReact's design aligns well with the requirements of SPA architecture.Component-based design allows large interfaces to be broken into smaller, manageable pieces. Each component encapsulates its logic, state, and rendering behavior.The Virtual DOM helps maintain performance by minimizing unnecessary DOM updates. Meanwhile, React's state-driven model ensures that the interface stays synchronized with application data.Together, these ideas provide a structured approach to managing dynamic user interfaces.This combination of performance and developer ergonomics helped React become one of the most widely used frameworks for building modern web applications.Final wordsSingle Page Applications represent a shift in how the web is used. Instead of treating the browser as a document viewer, modern applications treat it as an execution environment for complex software.This architectural shift allows web applications to behave more like native software, delivering responsive interfaces and fluid user experiences.Understanding SPAs means understanding three core ideas. First, the browser now handles much of the interface rendering that used to happen on the server. Second, applications persist in the browser and update the interface dynamically rather than rebuilding pages. And third, frameworks such as React provide the abstractions needed to manage the resulting complexity.Once you view the web through this lens, many modern frontend patterns begin to make sense. Component-based architectures, state-driven rendering, and client-side routing all exist to support the same goal: building web applications that behave like real software rather than collections of pages.These concepts are explored further in Full-Stack React, TypeScript, and Node (2nd Edition), currently available in early access for PacktPub subscribers.Preorder now!This Week in the News📊 State of React Native results are out: The latest State of React Native survey is live, offering a snapshot of how developers are using the framework today. It covers adoption trends, satisfaction, tooling, and common pain points across the ecosystem. Worth a look if React Native is part of your stack or roadmap.🤖 Cloudflare rebuilds Next.js as AI reshapes the commercial open source model: Cloudflare surprised the developer community this week by claiming that a single developer rewrote Next.js in just one week using AI tools, spending about $1,100 in tokens. The experiment highlights how quickly large codebases can now be recreated with AI-assisted development, raising new questions about the speed and economics of building complex frameworks.📈 Next.js 16 upgrade triggers unexpected request spikes: Some developers upgrading from Next.js 15 to 16 report higher server request volume and increased response latency. The change appears to put more strain on backend infrastructure, which could mean higher compute usage and increased hosting costs in production. A useful read if you’ve ever been surprised by a framework behaving differently outside local dev.🔐 When a GitHub Issue Becomes a Supply Chain Attack: A security write-up explains a vulnerability chain called Clinejection that exploited an AI GitHub issue bot. The attack combined prompt injection with CI cache poisoning to publish a malicious package. It’s a good reminder that AI agents in developer workflows introduce a new kind of supply chain risk.💬 Vercel introduces the AI Chat SDK: Vercel has released a new Chat SDK aimed at simplifying how developers build conversational AI interfaces. The SDK provides primitives for streaming responses, handling message state, and integrating with multiple AI providers. If you’re building AI features into web apps, it removes a lot of the plumbing typically required for chat-style interactions.Beyond the Headlines🔄 Migrating from TypeScript 5.x to 6.0: This GitHub gist explores some extreme TypeScript type patterns that push the compiler surprisingly far. It’s a fascinating look at how conditional types, inference, and recursion turn TypeScript into something close to a compile-time programming language.🤖 AI Is Writing More Software, but Who Checks the Output?: Lean creator Leo de Moura explores what happens if AI systems eventually generate the majority of production code. The essay looks beyond productivity gains and asks deeper questions about verification, correctness, and how we maintain trust in software when humans are no longer writing most of it. A thoughtful perspective on where AI-assisted development could lead.🎞 Web Performance with Image Sprites: Sprite animations are an old web technique that still works surprisingly well today. Josh Comeau walks through how sprite sheets function, how to implement them with modern CSS, and when they’re preferable to other animation approaches. A great refresher for anyone building playful UI interactions or performance-friendly animations.🧠 Patterns for building agentic systems: Simon Willison breaks down emerging patterns in “agentic engineering” workflows. The article looks at how developers are structuring AI agents that can reason, call tools, and coordinate tasks across systems. If you’re experimenting with agent-style architectures, this piece highlights the design patterns that are starting to emerge.Tool of the Week🧩Build Custom Rich Text Editors with YooptaIf you're building apps that need rich text editing, finding a flexible editor can be tricky. Many solutions are either too rigid or difficult to extend.Yoopta Editor is a modern, open source rich text editor framework built for React. It uses a block-based architecture, making it easier to customize editing experiences, add plugins, and control how content is structured.For developers building CMS tools, collaborative editors, or AI-assisted writing interfaces, Yoopta offers a flexible foundation without forcing a fixed editing model.That’s all for this week. Have any ideas you want to see in the next article? Hit Reply!Cheers!Editor-in-chief,Kinnari ChohanSUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND!*{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
16 Mar 2026
13 min read
Save for later

WebDevPro #130: Rethinking State Management in Modern React

Kinnari Chohan
16 Mar 2026
13 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #130 Rethinking State Management in Modern React Crafting the Web: Tips, Tools, and Trends for Developers Most Spring Boot projects stop at REST APIs Real systems require service discovery, API gateways, centralized configuration, and built-in resilience. In this live, hands-on workshop, you’ll build a working microservices system end-to-end, define service boundaries, wire up discovery, configure a gateway, and handle failures properly. 🎟 Register now and get 40% off with code SAVE40 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! If you’ve worked with React for a while, you’ve probably run into the same recurring question: how should we manage state in this application? State management is one of those topics that almost every React team ends up debating sooner or later. Should everything live in context? Do we need a global store? Would something like Zustand make things simpler? Those conversations usually focus on tools. But in many cases the real problem shows up earlier than that. In practice, React applications rarely become difficult to maintain because the wrong library was chosen. They become difficult to maintain because different kinds of state get handled in the same way. A form input, an API response, a UI filter, and a shared user object often end up managed with identical patterns. That is where complexity starts to creep in. In this article, we will look at the main categories of state that appear in React applications and how each category is best managed. By the end, you will have a clearer way to decide where state belongs in a React app, whether that means local component state, shared state, server data tools, or even the URL itself. Not all state belongs in the same place State in React applications usually falls into a few clear categories. Local state lives inside a single component. A dropdown menu, a modal visibility toggle, or a small UI interaction typically belongs here. Hooks such as useState or useReducer work well because the logic remains isolated. Server state represents data fetched from APIs or databases. This includes user profiles, product listings, or analytics data. The key difference is that the source of truth lives outside the application. Form state includes field values, validation errors, and submission status. Forms have their own lifecycle and benefit from specialized tools such as React Hook Form or React’s form hooks. URL state stores small pieces of UI state in route or search parameters. Tabs, filters, pagination, and search queries often belong here. Shared state exists when multiple components need access to the same data. Authentication details, application settings, or user preferences are common examples. Understanding these categories clarifies an important point. React state is not a single problem. It is a set of related problems that require different solutions. The easiest state to manage is the state you never store One of the most common sources of complexity in React applications comes from duplicated state. Consider a list of items where the interface displays only active entries. A common pattern stores both the original list and a filtered version of it. const [items, setItems] = useState([...]) const [filteredItems, setFilteredItems] = useState([]) useEffect(() => { setFilteredItems(items.filter(item => item.active)) }, [items]) This introduces synchronization logic that React must maintain. A simpler approach derives the filtered list directly from the original state. const [items, setItems] = useState([...]) const filteredItems = items.filter(item => item.active) The application now maintains a single source of truth. React calculates the derived value when needed. Derived state reduces bugs and simplifies reasoning about data flow. Many React components become easier to maintain once unnecessary state variables disappear. Shared state is where React applications become complicated Local state remains predictable because it stays within a component. Shared state changes that dynamic because multiple components read and update the same values. Developers often encounter this situation when user data, permissions, or global settings must appear across different sections of the interface. The simplest approach begins with prop drilling. A parent component holds the state and passes it through props to child components. This technique often receives criticism, yet it works well for a small number of adjacent components. It also keeps data flow explicit and easy to trace. Problems appear when the component tree becomes deeper. Intermediate components may receive props they do not use, simply to pass them further down the hierarchy. At this stage, many teams introduce React context. Context allows components to access shared values without passing them through each level of the tree. A provider component stores the shared state and exposes it to any descendant component. This pattern simplifies access but introduces a different trade-off. When context values change, every consumer beneath the provider may re-render. For small applications this rarely matters. In larger interfaces the rendering behavior becomes harder to control. Libraries such as Zustand approach shared state from a different angle. Zustand creates a centralized store and allows components to subscribe only to the pieces of state they require. const userName = useUserStore(state => state.userName) Components update only when the subscribed value changes. This selective subscription reduces unnecessary renders and keeps the shared state logic compact. Context and Zustand both solve shared state challenges, but they operate at different scales. Context works well for moderate application state. Zustand becomes attractive once shared state spreads across larger parts of the interface. Server state follows a different lifecycle Data fetched from external services introduces a different category of state entirely. Server data involves caching, background refetching, loading indicators, and stale data management. Handling these concerns with useState and useEffect often leads to repetitive code and fragile synchronization. Libraries such as TanStack Query address this layer directly. They treat server data as cached resources rather than ordinary component state. Components request data using a shared query key. const { data } = useQuery({ queryKey: ['user', userId], queryFn: fetchUser }) TanStack Query stores the response in a client cache. Other components requesting the same query receive the cached result rather than triggering another network request. This model separates server data concerns from UI logic. React components remain focused on rendering rather than managing asynchronous data lifecycles. Some state belongs in the URL Another overlooked location for state is the browser address bar. Interfaces often contain UI state that describes how a page is being viewed. Active tabs, filter selections, search queries, and pagination indexes all fall into this category. Storing this information in URL search parameters creates several benefits. The state becomes shareable through links. Refreshing the page preserves the interface configuration. Browser navigation also restores previous UI states naturally. Framework hooks such as useSearchParams and routing utilities make this pattern straightforward. const params = useSearchParams() const activeTab = params.get('tab') Many teams duplicate this information inside React state and attempt to synchronize it with the URL. Removing that duplication simplifies the architecture. The address bar can act as a reliable source of truth for small pieces of UI state. Choosing the right home for state Modern React offers many tools for managing state, but the real skill lies in recognizing where each type of state belongs. Local UI interactions usually remain simplest when handled with component state. Values that can be computed from existing data should be derived instead of stored. Data fetched from APIs benefits from tools designed for server state, such as TanStack Query, which handle caching and refetching automatically. UI state that affects navigation, such as active tabs or filters, often works best when stored in URL parameters. Shared client state can start with straightforward prop passing and evolve into solutions such as context or Zustand when multiple components need coordinated access. The key takeaway is not which library to choose. It is learning to recognize the nature of the state problem in front of you. Once you can distinguish between local, shared, server, form, and URL state, the architecture decisions become much clearer. Instead of forcing every problem into the same pattern, each piece of state can live in the place where it is easiest to manage. That shift in thinking leads to React applications that are easier to reason about, easier to scale, and far less prone to the state management issues that often slow teams down. Build real skills in LLMs and agentic AI with this 20+ course Packt bundle, featuring titles like Learn Python Programming, 4E and The LLM Engineer’s Handbook. Learn how to design and deploy intelligent systems while supporting World Central Kitchen. 👉 Grab the LLM & Agentic AI Career Accelerator bundle This Week in the News 🧠 TypeScript 6.0 RC prepares the ecosystem for the Go-powered compiler:TypeScript 6.0 RC has landed and it marks an important transition for the language. This release functions largely as a stepping stone toward TypeScript 7.0, which will introduce a new native compiler written in Go. The RC itself contains only a few small changes compared to the beta, but the bigger shift lies in the groundwork being laid for the next generation of the toolchain. Required updates to tsconfig.json help projects align with upcoming architecture changes, giving teams time to prepare before the performance gains of the Go-powered compiler arrive later this year. ⚛️ SolidJS 2.0 beta introduces first-class async and a redesigned reactive core: SolidJS has entered the 2.0 beta phase after several years of experimental work on its next-generation reactive system. The release introduces major architectural changes including a rewritten signals implementation, deterministic batching, and first-class async support built directly into the framework’s primitives. New patterns such as action and optimistic state helpers aim to make server mutations and UI updates easier to manage, while updates to control flow and rendering bring several breaking changes developers will notice quickly. The beta sets the stage for broader ecosystem updates ahead of the stable 2.0 release. ☁️ Astro 6 introduces a Rust compiler and deeper Cloudflare alignment:Astro 6 arrives as the framework’s first major release since its acquisition by Cloudflare in January, and the platform direction is already becoming clearer. The update introduces an experimental Rust compiler that will eventually replace the original Go-based .astro compiler, promising faster builds and a modernized compilation pipeline. Development workflows also improve through Vite’s new Environment API, which allows developers to run the exact production runtime during development. Astro also introduces a new Fonts API that simplifies custom font handling across projects. 🌊 Cloudflare pushes for a simpler modern JavaScript Streams API:Cloudflare engineers are questioning the design of the Web Streams API, arguing that it reflects an earlier era of JavaScript before patterns like async iteration became common. The result is an abstraction that often feels overly complex, with specialized readers, locking mechanics, and extra boilerplate. Cloudflare proposes a simpler model built on modern JavaScript primitives that could improve both ergonomics and performance. Early benchmarks suggest potential speedups of up to 120× across runtimes such as Node.js, Deno, and Workers. In a related talk, James Snell explains how an async-iterator-driven approach could make streaming code easier to reason about for developers working with edge platforms and large data pipelines. 🎥 Watch the talk: https://www.youtube.com/watch?v=abbeIUOCzmw Beyond the Headlines 🤖 Literate programming may finally make sense in the AI agent era: Donald Knuth’s idea of literate programming asked developers to write code as a narrative meant for humans, with the compiler following along. For decades the concept remained mostly academic. This piece revisits the idea through the lens of AI-assisted development, where agents read, analyze, and generate code alongside developers. Clear explanations, structured reasoning, and intent-rich programs suddenly become far more valuable. The argument is simple but compelling: the rise of coding agents may finally make literate programming practical. 🐘 Just use Postgres and delay the infrastructure sprawl:Many modern stacks quickly grow into a collection of specialized tools: queues, search services, analytics systems, and caches. This article argues that much of that complexity arrives too early. PostgreSQL already includes powerful capabilities such as JSON support, full-text search, background jobs, and extensions that cover a surprising range of workloads. For many products, a single Postgres database can carry far more responsibility than teams assume. The takeaway is pragmatic: lean on Postgres longer and introduce new infrastructure only when the workload genuinely demands it. ⚡Building a real-time collaborative to-do app with Jazz and Vue: Real-time collaboration often brings complex backend logic, synchronization challenges, and WebSocket plumbing. This tutorial shows a simpler path by building a collaborative to-do app using Jazz and Vue. The stack manages shared state and synchronization automatically, allowing the interface to update instantly as multiple users interact with the same data. The walkthrough focuses on how collaborative state flows through the application and how the UI reflects updates in real time. It offers a practical introduction to modern tooling for building collaborative applications. ⚛️ Why React needed Fiber and what problem it actually solved:React Fiber is one of the biggest architectural changes in React’s history, yet its purpose is often misunderstood. This deep dive explains why the original reconciliation algorithm struggled with large or complex updates. Fiber introduced a scheduling model that allows React to pause, resume, and prioritize rendering work instead of processing everything in one blocking pass. That change laid the foundation for features such as concurrent rendering and smoother user experiences under heavy workloads. Tool of the Week 🛠️ VMPrint deterministic PDF generation without headless browsers Print-to-PDF pipelines often rely on headless Chrome, bringing along browser quirks and inconsistent rendering. VMPrint takes a different approach with a pure TypeScript typesetting engine that bypasses the DOM entirely and computes layout through typographic math. The result is deterministic output across browsers, Node.js, and edge runtimes like Cloudflare Workers. Given identical input, VMPrint guarantees identical layout down to the sub-point position of every glyph. For teams building document generation pipelines or publishing systems, it offers a precise and reproducible alternative to browser-based rendering. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
30 Mar 2026
12 min read
Save for later

WebDevPro #132: Async Code That Looks Fine but Fails in Production

Kinnari Chohan
30 Mar 2026
12 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #132 Async Code That Looks Fine but Fails in Production Crafting the Web: Tips, Tools, and Trends for Developers Most Spring Boot projects stop at REST APIs Most Spring Boot developers stop at REST APIs. That’s enough to build demos but not to build systems that survive production. The real work sits beyond that. Service discovery, resilience, observability, config management are what separate working code from systems that hold up under pressure. You don’t pick this up from tutorials. It comes from building systems, making trade-offs, and seeing them run. Tomorrow, you get exactly that. Live. From scratch. With Simon Martinelli and Josh Long. Only 6 spots left. 🎟 Use code SPRING40 for 40% OFF 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! Have you ever written async code that looked perfectly fine, only for it to behave unpredictably later? It’s one of those things that feels obvious while coding, but starts to fall apart once real conditions come into play. Asynchronous programming is fundamental to Node.js. The event loop keeps everything moving, delegating work and picking it back up when results return. On paper, it feels straightforward. You write code in sequence, so it should run that way too. But that’s where things get tricky. Execution is shaped by timing, scheduling, and resource contention. These are not visible in the code itself. What looks sequential can run concurrently. What feels predictable can change under load. Many real-world issues don’t come from syntax errors, but from how async behavior interacts with shared resources and execution order. This week’s deep dive breaks down where these assumptions fail and what it takes to make async systems behave reliably. Before we get into it, here’s this week at a glance: 🟦 TypeScript is preparing for a compiler rewrite 📦 pnpm is redesigning how dependencies are stored 🤖 Next.js is pushing toward AI-first app development ⚡ Claude dropped SSR for speed gains 🧩 Storybook is becoming AI-readable infrastructure When Async Execution Breaks Assumptions A common source of failure is incorrect assumptions about execution order. Consider a case where two operationsattempttowrite tothe same file. The code may appear sequential, but without explicit coordination, the operations execute independently. In such scenarios, the following pattern is oftenobserved: async functionraceCondition() { const filename = ... await unlink(filename) writeFile(filename, 'Written from first promise\n',{ flag: 'a' }) writeFile(filename, 'Written from second promise\n',{ flag: 'a' }) } At first glance, this code appears correct because two write operations are triggered one after the other.However, the absence of awaiting these operations means they run concurrently. Both operationsattemptto access the same file at the same time. This leads to inconsistent results, where the order of writes varies across executions. This behavior is known as a race condition. The issue is not syntax, but incorrect assumptions about how asynchronous execution works. Concurrency Does Not Guarantee Order In asynchronous systems, starting operations sequentially does not guarantee sequential execution. Each asynchronous call creates its own execution path, and these paths are resolved independently based on system timing. When multiple operations target the same resource, they compete for access. Without explicit coordination, the runtime does not guarantee which operationcompletesfirst. The outcome becomes dependent on timing rather than intent. This variability may not appear during development but becomes more prominent under production load, where multiple operationsexecutesimultaneously. Coordinating Access: Controlling Async Behavior To prevent race conditions, access to shared resources must be controlled. One approach is to introduce a mechanism that ensures only one operation interacts with the resource at a time. The following structuredemonstratesthis approach: classFileWriter{ #isWriting = false static instance = null constructor() { if (!FileWriter.instance) { FileWriter.instance= this } return FileWriter.instance } asyncwriteFile(filename, data) { if (this.#isWriting) { awaitsetTimeout(250) returnthis.writeFile(filename, data) } this.#isWriting= true const result = awaitwriteFile(filename, data, { flag: 'a' }) this.#isWriting= false return result } } This implementation introduces a lock mechanism. If a write operation is already in progress,subsequentoperationswaitbefore retrying. This ensures thatwritesoccur sequentially rather than concurrently. With coordination in place, execution becomes predictable. Without it, asynchronous code may behave inconsistently under real conditions. Callback Hell: When Async Structure Breaks Readability Asynchronous issues are not limited to executionorder. They also affect howcodeis structured. Deeply nested callbacks create code that is difficult to read andmaintain. An example of this structure is shown below: stepOne((err,resultOne) => { stepTwo(resultOne, (err,resultTwo) => { stepThree(resultTwo, (err,resultThree) => { console.log(resultThree); }); }); }); Although the code executes correctly, the nested structure makes it harder to follow the flow of data and control. Error handling is repeated at each level, increasing complexity. As the number of steps increases, the difficulty ofmaintainingand debugging the code also increases. The Event Loop and Hidden Blocking Node.js relies on non-blocking operations tomaintainperformance. The event loop processes tasks and delegatesworkwhen possible, allowing other operations to continue executing. However, not all operationsare non-blocking. Some APIs perform blocking I/O, which pauses execution of the entire program until completion. This prevents the event loop from handling other tasks. For example, cryptographic operations can block the main thread when executed synchronously. An asynchronous alternative allows work to be delegated externally: generateKeyPair("rsa", {modulusLength:1024 }, () => {}) The asynchronous version allows other operations to continue, while the synchronous version blocks execution. Under production load, blocking operations can significantly reduce system responsiveness. Async Does Not Always Mean Non-Blocking A common misconception is that wrapping a function in asynchronous code makes it non-blocking. This is not always true. If the underlying operation is blocking, it will still block execution. In such cases, performance improvements come from reducing how often the operation runs rather than changing how it is invoked. For example, caching results avoids repeated expensive computations: letcachedSignature= null; if (!cachedSignature) { cachedSignature=signData(...) } This approach improves throughput by reducing execution frequency rather than altering execution style. Async Behavior Under Load Many asynchronous issues only become visible under load. In controlled development environments, operations often execute in predictable sequences, and resource contention is minimal. As a result, code that appears stable during testing can behave differently when multiple operations are triggered at the same time. Race conditions become moreapparentwhen concurrent requests attempt to access ormodifythe same resource. What may appear as an occasional inconsistency during development can become a frequent issue when the same code is executed repeatedly under higher traffic. The lack of coordination between asynchronous operations leads to unpredictable results, making these issues harder to reproduce and debug. Blocking operations also have a more pronounced impactunderload. When a synchronous task runs on the main thread, it prevents the event loop from processing other incoming requests. In low-traffic scenarios, this delay may not be noticeable. Under production conditions, where many requests arrive simultaneously, blocking behavior can cause cascading delays, reducing overall responsiveness. Repeated execution of expensive operations further amplifies the problem. When the same computation is performed for every request without caching or reuse, system resources are consumed unnecessarily. This reduces throughput and increases response times, especially when multiple requests trigger the same operation concurrently. Anotherimportant factoris timing variability. Asynchronous execution depends on system scheduling, resource availability, and workload distribution. Under load, these factors fluctuate more significantly, increasing the likelihood of inconsistent outcomes. Code that relies on implicit ordering or timing assumptions becomes less reliable as concurrency increases. These issues highlight that asynchronous behavior is not only about writing non-blocking code, but also about understanding how that code behaves when multiple operations interact simultaneously. Without coordination, control over execution order, and careful management of shared resources, asynchronous systems can produce inconsistent results under real-world conditions. Final words Asynchronous programming enables Node.js to handle multiple operations efficiently, but it also introduces complexity in execution order, resource access, and performance. Many failures are caused by incorrect assumptions about how asynchronous code behaves. These issues oftenremainhidden during development and only surface under production conditions. Understanding how asynchronous code interacts with the event loop, shared resources, and system constraints is essential for building reliable applications. This Week in the News 🟦TypeScript 6.0 is really about TypeScript 7.0: TypeScript 6.0 quietly sets the stage for a bigger shift. This release moves the ecosystem closer to a Go-powered native compiler planned for TypeScript 7.0, with clear signals that performance and build speed are about to take a serious leap. It focuses less on surface-level features and more on groundwork that could reshape how large codebases compile and scale. 📦 pnpm 11 Beta just changed how dependencies are stored: pnpm 11 Beta offers a glimpse into where package management is heading. The shift to a SQLite-powered store improves lookup speed and reliability, while a broader config overhaul simplifies how projects define and share settings. Stricter build security is now enabled by default, reflecting a growing focus on supply chain safety. This release feels less like an incremental update and more like a rethink of how dependencies are stored and secured. 🤖Next.js just made AI apps feel native: Next.js 16.2 leans deeper into AI-native development. The update reshapes how developers connect model output to real interfaces, tightening the loop between prompting and product. The direction is becoming clear. Next.js is evolving into a foundation for AI-powered applications, not just a frontend framework. ⚡Why Claude dropped SSR for a Vite-powered setup: Anthropic’steam shared how they made Claude and its desktop apps meaningfully faster by moving away from SSR to a static setup using Vite andTanStackRouter. The shift highlights a growing pattern where speed and responsiveness win over traditional rendering models, especially for AI-heavy interfaces that demand instant feedback. 🧩 Storybook MCP brings AI into your UI workflow:Storybook’s latest update introduces an MCP server that lets coding agents understand your components at a deeper level. Instead of guessing structure, AI can now access metadata, generate stories, write tests, and even help fix bugs with more context. It signals a shift where component libraries are no longer just for developers, but also for the tools assisting them. Beyond the Headlines 🧠Fix your Next.js errors without exposing your code: Debugging production errors in Next.js often means staring at unreadable stack traces. This guide walks through setting up source maps withSentryso errors point back to your actual code, not minified chunks. The key detail is balance. You get full visibility in Sentry while keeping source maps out of the browser, which protects your code and improves debugging at the same time. 📊 Most open source projects are already accepting AI code:Phil Eaton surveyed 112 major source-available projects to understand how they handle AI-assisted contributions. The results show a clear trend. Most projects already allow or have accepted AI-generated code, with only a handful enforcing outright bans. The takeaway is not just policy, but reality. AI is already part of how modern open source evolves, and governance is still catching up ⚛️The React quirks you hate are actually fundamental: Some ofReact’smost disliked patterns, like deferred state updates and dependency arrays, are not accidental complexity. This piecearguesthey reflect deeper constraints of asynchronous UI systems. Once you look past the friction, they reveal problems every framework eventuallyhas tosolve, even the ones trying to replace React. 🧩Small programming tricks shape how your code scales: Tiny habits compound. This piece explores how small implementation choices, often dismissed as style or preference, quietly influence readability, maintainability, and long-term velocity. It is a reminder that good engineering is rarely about big rewrites. It is built through consistent, low-level decisions that add up over time. Tool of the Week ✍️ Draw it once, animate it instantly Stroke turns rough sketches into production-ready animations. You draw directly in the browser, and it generates Motion-based code you can drop into a React or Next.js component. Under the hood, it converts your strokes into SVG paths and animates them without manual setup, making it ideal for signatures, logos, or playful UI details. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
23 Mar 2026
14 min read
Save for later

WebDevPro #131: Building Reliable Applications with Cursor: A Spec-Driven Workflow for Incremental Development

Kinnari Chohan
23 Mar 2026
14 min read
Crafting the Web: Tips, Tools, and Trends for DevelopersAdvertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }WebDevPro #131Building Reliable Applications with Cursor: A Spec-Driven Workflow for Incremental DevelopmentCrafting the Web: Tips, Tools, and Trends for DevelopersMost Spring Boot projects stop at REST APIsReal systems require service discovery, API gateways, centralized configuration, and built-in resilience. In this live, hands-on workshop, you’ll build a working microservices system end-to-end, define service boundaries, wire up discovery, configure a gateway, and handle failures properly.🎟 Register now and get 40% off with code SAVE40📢 Important: WebDevPro is Moving to SubstackWe’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com.To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule.Substack will also give you more control over your subscription preferences if you decide to adjust them later.Welcome to this week’s issue of WebDevPro!If you’ve been experimenting with tools like Cursor, you’ve probably had that moment where the output looks promising but not quite right. You tweak the prompt, try again, and end up chasing the result instead of building something stable.What tends to work far better is not bigger prompts or more detailed instructions, but a shift toward a spec-driven, incremental workflow. One that mirrors how experienced developers already build systems, but adapts it to work effectively with AI-assisted coding environments.In this article, we want to walk through a more reliable way to approach this. By the end, you’ll have a practical workflow for structuring your projects, breaking down features, and working with AI coding tools in a way that actually scales beyond small experiments.Why direct prompting breaks down in real projectsIt is tempting to treat Cursor as a high-powered code generator. Describe the app, mention the tech stack, and expect a complete implementation.That approach holds up for small, disposable prototypes. It starts to fracture as soon as the project has multiple features, shared state, or evolving requirements.There are a few reasons for this.First, ambiguity compounds quickly. Even a well-written prompt leaves room for interpretation. The model fills in those gaps based on patterns, not your intent.Second, outputs are inherently variable. The same prompt can produce slightly different structures, naming conventions, or flows. This makes it difficult to build predictably.Third, you give up control of the system’s shape. Instead of designing the architecture, you are reacting to whatever the model generates.The result is a loop of correction rather than a process of construction.A more reliable approach: constrain, decompose, iterateA more effective pattern is to treat the tool as a collaborator that works best within clearly defined boundaries.The workflow is straightforward:Define what the system should doBreak it into discrete units of workImplement those units incrementallyRefine through feedbackThis is familiar territory. The difference is that your artifacts, like specifications and task lists, are now also guiding the model.Start with a specification that removes guessworkBefore opening Cursor, it helps to define how the application should behave in concrete terms. Consider a simple example: a math practice application for children.At a glance, it sounds trivial. In practice, it involves a number of decisions:What kinds of operations are supportedHow questions are presentedHow users interact with answersHow incorrect attempts are trackedWhat metrics are surfaced in a statistics viewWriting this down in a structured format forces clarity. It also creates a shared reference point for every subsequent step.One useful approach is to draft the specification in Markdown using a general-purpose language model. The key is not the initial output, but the interaction that follows. Asking the model to clarify uncertainties before generating the spec often leads to better results.Once you have a draft, it is worth reviewing line by line. Fill in gaps, remove contradictions, and add constraints that matter to you. Visual expectations, state transitions, and edge cases are all worth capturing early.The specification becomes less of a document and more of a contract. It defines intent in a way that both you and the tool can consistently refer back to.Translate the specification into a phased TODO listWith a clear specification in place, the next step is to decompose it into manageable units of work.Instead of thinking in terms of “build the app,” the focus shifts to “implement the next smallest meaningful piece.” A structured TODO list works well for this. Features are grouped into phases, and each phase contains tasks that can be completed independently.For example: Phase 1: Project setupInitialize React applicationConfigure styling systemCreate base routing and placeholder pages Phase 2: Core interactionGenerate math questionsRender question cardsImplement answer selection logicThis breakdown serves two purposes: It reduces cognitive load. You are no longer juggling the entire system in your head. It also gives Cursor a much clearer target. Instead of interpreting a broad request, it can focus on a well-defined task.Build incrementally and keep the scope tightWith the TODO list in place, you can begin implementing features in Cursor. The important shift here is how you frame your prompts. Rather than asking for a full implementation, you anchor the request to specific artifacts and a limited scope:Use the specification and TODO list to complete Phase 1. Mark completed tasks.This keeps the model grounded. It knows where to look for context and what success looks like. In the case of the math practice app, the first phase might result in:A scaffolded React projectBasic routingPlaceholder pages such as Home, Practice, Stats, and SettingsAt this stage, the application is simple, but functional. That is intentional. Each phase establishes a stable base for the next.Iterate deliberately instead of restartingOnce the initial output is in place, the next step is refinement.It is common for the first version of the UI or structure to feel underdeveloped. The instinct might be to rewrite the prompt or regenerate the feature from scratch. A more effective approach is to iterate on top of what already exists.For example:Improve the homepage layout. Add clearer navigation and better spacing.This kind of prompt is focused and contextual. It builds on existing work rather than replacing it.Over time, this creates a steady progression:Initial structureIncremental improvementsTargeted fixesEach step is small, but collectively they move the system toward a more polished state.Designing for variability instead of fighting itOne characteristic of working with language models is that outputs are not perfectly consistent. Even with the same inputs, you may see variations in structure, naming, or implementation details. Rather than trying to eliminate this variability, it helps to account for it in your workflow.Specifications anchor intent.TODO lists constrain scope.Incremental development limits the blast radius of changes.Together, these reduce the impact of variation. You retain control over the system, even as the underlying outputs shift slightly.A closer look at a single featureTo make this more concrete, consider the first phase of the math practice application. The goal is to establish the basic application shell. You begin with two inputs:A specification that defines pages and navigationA TODO list that outlines setup tasksThe prompt is simple and scoped:Complete Phase 1 using the provided specification and TODO list.Cursor initializes the project, sets up dependencies, and creates the necessary files. You run the application locally and see a basic interface with navigation between pages. At this point, the system is functional but minimal. You then refine:Improve the visual structure of the homepage and navigation.The tool updates components, adjusts layout, and introduces better spacing. You review the changes, keep what works, and continue. This pattern repeats for each feature. The system grows in layers, each one built on a stable foundation.Patterns that hold up in larger projectsA few patterns consistently make this approach effective. Clear specifications reduce the need for corrective prompts later. They shift effort from fixing outputs to shaping intent early.Breaking work into smaller tasks improves reliability. Each unit is easier for the model to interpret and implement correctly.Iteration keeps progress steady. Instead of waiting for a perfect result, you move forward through a series of improvements.Documentation becomes an active part of the process. Specifications and TODO lists evolve alongside the code, keeping everything aligned.Most importantly, you remain in control of the system’s design. The tool accelerates execution, but the direction still comes from you. But this workflow is not automatic. It depends on a few habits.If the specification is vague, the outputs will reflect that.If tasks are too large, the results become inconsistent again.If changes are accepted without review, small issues compound over time.The tool amplifies the process you bring to it. Structure leads to leverage. Lack of structure leads to friction.Closing thoughtsWorking with Cursor is less about mastering prompts and more about shaping a reliable development process. Once you move away from one-shot generation and toward a spec-driven, incremental approach, the experience changes. The tool becomes more predictable. The codebase becomes easier to reason about. Progress feels steadier.In many ways, this is not a new way of building software. It is a return to fundamentals, adapted for a new interface. The difference is that when the structure is right, the speed at which you can move begins to compound.If you want to go deeper into this way of working, especially with hands-on examples and a full project walkthrough, this approach is explored in detail in Vibe Coding with Cursor. It’s a practical guide to building real applications using these patterns, and a useful next step if you’re looking to move beyond experimentation into something more structured.This Week in the News🤖 OpenAI to acquire Astral: OpenAI is acquiring Astral and its widely used open-source Python developer tools, including uv, Ruff, and ty. The move strengthens OpenAI’s push into developer tooling, bringing core workflows like dependency management, linting, and type checking closer to its ecosystem. The goal is to accelerate Codex beyond code generation, positioning it to operate across the full development lifecycle, where it can work directly within the tools developers already use.🗓️ Why JavaScript still struggles with dates: The JavaScript new Date(someString) constructor is notoriously over-eager, stemming from legacy C++ parsers in engines like V8 and SpiderMonkey that aggressively "guess" date components to maintain backwards compatibility. This leads to absurd "hallucinations" where the engine prioritizes finding a year at any cost. Beyond these quirks, the parser's inconsistency is a frequent source of production bugs; for instance, new Date("2024-03-25") is treated as UTC, while adding a time element like T00:00 shifts it to Local Time, often causing "off-by-one-day" errors for users in different time zones. To avoid silent data corruption, such as new Date(null) defaulting to the 1970 Unix Epoch, developers should abandon these loose string constructors in favor of strict ISO 8601 formatting or the modern, predictable Temporal API.▲ Next.js 16.2 updates:Next.js 16.2 introduces a set of refinements focused on improving performance, stability, and the overall developer experience. The release builds on recent changes to the app router and server components, with updates that make common workflows more predictable and easier to manage. The direction remains consistent, with Vercel continuing to smooth out rough edges while aligning Next.js more closely with modern React patterns and production needs.⚡Vite 8 and Void, the fill-stack pivot: The Vite ecosystem is expanding beyond build tooling. Alongside the release of Vite 8, which brings updates focused on faster builds and a more streamlined developer experience, the VoidZero team has introduced Void, a Vite-native deployment platform built on Cloudflare Workers. Void aims to turn Vite apps into full-stack applications by bundling capabilities like databases, KV storage, object storage, and AI inference directly into the workflow. Together, these updates signal a shift toward a more integrated Vite stack that spans both development and deployment.⚖️ No AI code in Node.js core? A new petition sparks debate:A long-running discussion around AI-assisted development in OpenJS Foundation projects has taken a new turn. A Node.js contributor has started a petition urging the TSC to restrict AI-generated code from being merged into Node’s core, following concerns raised alongside a large 19K LOC PR. The debate touches on deeper questions around code quality, maintainability, and ownership as AI becomes a common part of development workflows.🧩 How TanStack approaches modern developer tooling:TanStack shares a set of ecosystem updates alongside a detailed talk that breaks down the thinking behind its tooling approach. The focus stays on building framework-agnostic, composable primitives that can scale across different application architectures without locking developers into a specific stack. The talk offers a deeper look into how these decisions play out in practice, from managing server state to structuring UI logic, and why flexibility and interoperability remain central to TanStack’s design philosophy.Beyond the Headlines📉 The hidden cost of “comprehension debt”:Addy Osmani introduces the idea of comprehension debt: the growing gap between how quickly code can be produced and how easily it can be understood. As tools (especially AI) accelerate code generation, the burden shifts to developers who need to read, debug, and maintain that code. It’s a useful lens for thinking about long-term code quality. Fast output is valuable, but only if teams can still reason about what they’ve built.🧠 React to Svelte migration with AI agents: Strawberry shares how it rewrote 130K lines of React code to Svelte in just two weeks using coding agents, cutting UI complexity and doubling performance in the process. The write-up goes deeper into why the team moved away from React’s rendering model toward Svelte’s compiler-first approach, and how AI-assisted workflows made a full rewrite practical at a scale that would usually take months. It’s a strong signal of how framework decisions, performance constraints, and AI-driven development are starting to converge in real-world systems.🎥 Rethinking how we build modern web apps: This talk explores how modern web development is evolving, touching on architecture, tooling, and the trade-offs developers face as applications grow in complexity. It’s a useful watch if you’re thinking about how today’s patterns scale in real-world systems.🚄 Understanding how JIT affects runtime performance: This post explores how Just-In-Time (JIT) compilation behaves in practice, looking at how execution performance evolves over time. It offers a deeper look at runtime optimizations and how they impact real-world performance characteristics.Tool of the Week🔍 Understand code changes with structural diffsTraditional diffs compare text line by line, which can make complex changes hard to follow. Difftastic takes a different approach by comparing the syntax tree of your code, helping you see what actually changed at a structural level.It supports multiple languages and highlights meaningful differences, making it especially useful when reviewing refactors or large code updates.That’s all for this week. Have any ideas you want to see in the next article? Hit Reply!Cheers!Editor-in-chief,Kinnari Chohan👋 Advertise with usInterested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps.SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND!*{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0
Kinnari Chohan
06 Apr 2026
11 min read
Save for later

WebDevPro #133: Rethinking Backend Communication for Real-Time Systems

Kinnari Chohan
06 Apr 2026
11 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #133 Rethinking Backend Communication for Real-Time Systems Crafting the Web: Tips, Tools, and Trends for Developers Job postings at Microsoft, Deloitte, Accenture, and KPMG are now listing Copilot Studio, Power Automate, and MCP as must-have skills. The fastest way to get there? Build a real enterprise AI agent in 2 days LIVE, with instructors from Microsoft and Confluent. Use code SAVE40 for 40% off. Grab your spot: April 18-19 → 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! Most backend systems are built around a simple assumption: the client asks, the server responds, and the connection ends. That model has worked for decades because it fits neatly with how the web evolved. It scales well, it’s easy to reason about, and most infrastructure is designed around it. But the moment you step into real-time features, that assumption starts to break. Chat apps, collaborative tools, live dashboards, multiplayer systems, and even notifications all share one requirement: the server needs to speak without being asked. That single shift changes how you design your backend, how you think about state, and how you handle communication altogether. This is where event-driven systems enter the picture. Before we get into it, here’s this week at a glance: 🤖 Cursor 3 is built around an agent-first workflow ⚙️ Next.js moves beyond Vercel with a new Adapter API 🤖 AI debugging shifts toward agent-driven workflows at Sentry ⚠️ Copilot incident highlights risks in AI-assisted coding ⚡ Trigger.dev reports major performance gains after switching to Bun 📦 npm workspaces finally make sense Where Request-Response Falls Short Consider a typical chat system built with REST: Fetch messages with GET /messages Send messages with POST /messages Poll regularly to check for updates Polling is the problem. You either: Poll frequently and waste resources, or Poll less often and introduce lag Even long polling only stretches the same model. The client is still responsible for initiating communication. Event-driven systems remove that constraint. Instead of asking repeatedly, the client stays connected and receives updates as they happen. Persistent Connections Change the Rules WebSockets make this possible by keeping a connection open between client and server. Both sides can send messages at any time. This introduces three key shifts: Communication becomes bidirectional The server can initiate updates The connection now carries state That last point is the tradeoff. Stateless systems are easy to scale. Stateful connections require you to manage: Connection lifecycle Reconnection Failure handling You gain responsiveness, but complexity increases. Why Abstractions Like Socket.IO Matter WebSockets provide the foundation, but they are intentionally minimal. Production systems need more than just a raw connection. Socket.IO adds: Automatic reconnection Fallback to long polling Event-based APIs Broadcasting and grouping Logical channels via rooms This shifts your backend from handling endpoints to handling events. Thinking in Events Instead of Endpoints In REST, you design routes and responses. In event-driven systems, you design interactions. Instead of: POST /messages GET /messages You work with: socket.emit('chat.message', message) socket.on('chat.message', (msg) => { // handle message }) The server becomes an event hub, and clients react to changes as they occur. This model aligns better with systems where state evolves continuously. Broadcasting and Scope A core capability of event-driven systems is broadcasting: io.emit('chat.message', payload) This allows one event to reach many clients instantly. But sending everything to everyone rarely works in practice. You need scope. Rooms provide that control: socket.join(room) io.to(room).emit('chat.message', payload) This lets you: Segment users Isolate conversations Reduce unnecessary updates Rooms are not just a feature. They are how you model context in real-time systems. When You Still Need Responses Event-driven systems are asynchronous by default, but not everything fits that model. Sometimes you need a response, such as fetching user data or validating an action. Socket.IO supports this through acknowledgments: const userInfo = await socket.emitWithAck('user.info', socket.id) This creates a hybrid model where event-driven flows and request-response patterns coexist. In practice, most systems rely on both. Authentication Becomes a Connection Concern In REST, authentication happens per request. With persistent connections, it happens during the handshake: io.use((socket, next) => { // verify token }) Once established, the connection is trusted. This introduces new questions: What happens when tokens expire? How do you revoke access in real time? How do you store credentials securely? Even token storage becomes more critical, as insecure storage mechanisms can expose credentials. Authentication is no longer a repeated check. It becomes part of connection management. The Tradeoff: State and Scaling The biggest difference between REST and event-driven systems is state. REST systems are stateless, which makes scaling straightforward. Any server can handle any request. Event-driven systems maintain active connections. That means: Servers track clients Messages must reach specific connections Load balancing becomes more complex At scale, this often requires additional infrastructure like shared message layers or coordinated routing. This is where the simplicity of REST becomes hard to replace. Choosing the Right Model Event-driven systems are not a replacement for REST. They solve a different class of problems. They work best when: Data changes frequently Users expect immediate updates Interaction is continuous They add unnecessary complexity when: Data is mostly static Caching is effective Real-time behavior is not critical A blog platform benefits from REST. A collaborative editor does not. Instead of focusing on tools, focus on interaction patterns, ask: Does the server need to push updates? Do users depend on real-time feedback? Is the system driven by events rather than requests? If yes, an event-driven approach is worth the tradeoff. Key Takeaways Real-time systems are less about adopting new tools and more about changing how you think about communication. Once the server is no longer passive, the entire shape of your backend begins to evolve. If you want to explore how these concepts come together in a full application, Modern Full-Stack React Projects by Daniel Bugl is a solid next step. This Week in the News 🤖 Cursor 3 introduces a unified workspace for coding with agents: Cursor 3 is a full redesign built around managing AI agents in one place. You can run multiple agents in parallel, move work between local and cloud, and go from generated changes to merged PRs without leaving the interface. The shift is not about replacing coding. It is about raising the level of abstraction. You spend less time juggling tools and more time coordinating how work gets done. ⚙️ Next.js is finally decoupling from Vercel: Next.js is making a clear move toward platform independence. The new Adapter API creates a shared contract that lets any provider support Next.js reliably, backed by a public test suite and collaboration across Cloudflare, Netlify, AWS, and more. The direction is hard to miss. Next.js is no longer just a framework tied to one deployment model. It is becoming an infrastructure that can run anywhere without compromises. 🛠️ Sentry is turning debugging into agent workflows: Sentry’s new cookbook is a collection of ready-made “agent recipes” designed to help AI systems find, debug, and fix issues faster. Instead of treating observability as a dashboard, it becomes something agents can act on directly. The direction is clear. Debugging is no longer just about seeing errors. It’s about giving agents the context to resolve them automatically. ⚠️ Copilot briefly inserted an ad into a pull request: A developer noticed Copilot had edited an unexpected ‘ad’ into a PR, raising questions about how AI tools modify code. GitHub’s Martin Woodward later clarified what caused the behavior and confirmed the feature has now been disabled. It’s a small incident, but a reminder that AI-assisted coding still needs clear boundaries and visibility. 🧩 Run Codex inside Claude Code: The Codex plugin brings OpenAI’s Codex directly into Claude Code, combining two AI systems into a single workflow. It allows developers to delegate tasks like code reviews and bug fixes to background jobs while continuing to work. The interesting shift here is orchestration. Instead of one assistant, developers are starting to coordinate multiple agents for different tasks. ☁️ Cloudflare is building a WordPress alternative for the edge: Cloudflare’s EmDash is positioned as a modern successor to WordPress, designed to run on Cloudflare’s edge or any Node.js server. It focuses on performance, simplicity, and a more developer-friendly architecture. The bigger signal is where platforms are heading. Content systems are moving closer to the edge, with infrastructure and CMS becoming tightly integrated. Beyond the Headlines 🧠 What it’s like to actually use Claude Code: Claude Code is getting attention, but this deep dive looks past the hype into how it actually behaves in real workflows. From code navigation to multi-step reasoning, the experience feels less like autocomplete and more like collaborating with a system that understands intent. The gap between writing code and orchestrating it is starting to shrink. 📦 npm workspaces finally make sense: As projects grow beyond a single folder, things start to break. Shared code drifts, dependencies duplicate, and workflows get messy. This guide breaks down how npm workspaces actually solve that by managing multiple packages in one repo with shared dependencies and seamless imports. The value is not just convenience. It is about building systems that scale without turning your codebase into a maintenance problem. 🔥 Trigger.dev got 5x performance by replacing Node with Bun: : Trigger.dev swapped Node.js for Bun in a latency-critical service and saw throughput jump from ~2k to over 10k requests per second. The deeper insight is not just speed. It shows how runtime choices are starting to matter again, especially for systems handling high concurrency and long-lived connections. 🔍 Why ChatGPT waits before you can type: A deep reverse-engineering effort by Buchodi digs into why ChatGPT sometimes blocks input until a background check completes. The analysis suggests Cloudflare’s Turnstile is not just verifying the browser, but checking whether the app itself has properly initialized, including parts of the React state. The bigger shift is subtle but important. Bot detection is moving beyond “is this a real browser” to “is this a real user interacting with a real app.” Tool of the Week 📺 Build your own YouTube-style player with ArtPlayer (demo) ArtPlayer is a full-featured HTML5 video player that gives you fine-grained control over the playback experience. From custom controls and subtitles to plugins and styling, it is designed to be deeply customizable without adding unnecessary complexity. If you need more than a basic video tag but don’t want to build everything from scratch, this strikes a practical middle ground. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
13 Apr 2026
14 min read
Save for later

WebDevPro #134: Rendering in Next.js is a system of trade-offs, not a feature checklist

Kinnari Chohan
13 Apr 2026
14 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #134 Rendering in Next.js is a system of trade-offs, not a feature checklist Crafting the Web: Tips, Tools, and Trends for Developers Grow your mobile apps efficiently, without ads! Do you rely on paid ads for mobile growth? Don't the rising costs and limited visibility make it harder to scale? Insert Affiliate gives you another option. It lets you run an affiliate channel for your app, where partners, creators, or communities drive users via tracked links, and you can tie installs, purchases, and subscriptions back to the source. Start tracking affiliate revenue 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! Most of us think rendering strategies were a matter of picking the “best” option. Server-side rendering felt powerful. Static generation felt fast. Client-side rendering felt flexible. The assumption was simple: choose one, commit, and move on. That mental model breaks down the moment your application grows beyond a few pages. In practice, rendering is not a decision you make once. It is a system of trade-offs you keep revisiting as your product evolves. Next.js does not force you into one approach. It gives you multiple levers and expects you to use them deliberately. The real shift is not technical. It is conceptual. You stop asking “Which rendering strategy should I use?” and start asking “Where should this piece of UI live, and when should it be rendered?” Before we get into it, here’s this week at a glance: 🧠 Copilot now asks another AI before trusting itself 🔐 Cloudflare just moved up the deadline for quantum-safe internet ⚙️ Node.js keeps shipping under-the-radar improvements ⚠️ How attackers are now targeting open source maintainers 🧩 Claude is not your software architect 🎥 The React hook most developers still ignore The illusion of a single rendering strategy Traditional frameworks leaned heavily in one direction. Server-rendered apps generated HTML on every request. Client-rendered apps shipped a JavaScript bundle and built everything in the browser. Static site generators pushed everything to build time. Each model worked well in isolation. Each also came with blind spots. Next.js breaks that boundary. You can render some pages at request time, others at build time, and still defer specific components to the client. This flexibility is powerful, but it also introduces a new responsibility: understanding the cost of each choice. Rendering is no longer about capability. It is about trade-offs across performance, scalability, user experience, and operational complexity. Server-side rendering is about control, not just freshness Server-side rendering still plays a critical role, especially when the content depends on the request itself. When a page is rendered on the server for every request, you gain control over what gets sent to the browser. You can safely access private APIs, validate data, and tailor responses per user. The browser receives fully formed HTML, which improves compatibility and helps search engines understand your content immediately. That control comes at a cost. Every request triggers computation. Every data dependency introduces latency. Even a well-optimized server will feel slower compared to serving a prebuilt file. Navigation between pages can feel heavier because each transition depends on server work. There is also an operational dimension. A server-rendered app is not just code. It is infrastructure. It scales with traffic, and that scaling has a cost. SSR shines when you genuinely need per-request freshness or personalization. It becomes a liability when used out of habit. A common mistake is defaulting to SSR for anything that “feels dynamic.” In reality, many of those pages do not need to be recomputed on every request. They only need to feel dynamic. Client-side rendering shifts responsibility to the browser Client-side rendering takes the opposite approach. Instead of sending meaningful HTML, the server delivers a minimal shell and a JavaScript bundle. The browser then builds the interface, fetches data, and manages state. This model feels fast after the initial load. Once the application is hydrated, navigation becomes seamless. Transitions are smooth. Interactions feel immediate. The app behaves more like a native experience. There is a reason this approach became popular. It reduces server workload significantly. The server becomes a delivery mechanism rather than a computation layer. You can scale more easily, especially in serverless environments. You also gain flexibility in how and when you fetch data. But the trade-offs are hard to ignore. The first load can be painfully slow on weak networks. Users may stare at an empty screen while JavaScript downloads and executes. Search engines see very little initial content, which can impact discoverability and performance scores . Client-side rendering works well when SEO is not a priority and when interactivity outweighs initial load performance. Think dashboards, internal tools, or authenticated user spaces. Even then, it should be a deliberate choice, not a default fallback. Static generation optimizes for speed and scale Static site generation flips the model again. Instead of rendering on the server per request or in the browser at runtime, you render at build time. The output is a static HTML file that can be served instantly. This approach is hard to beat in terms of performance. There is no computation at request time. The server simply returns a file. CDNs cache and distribute it globally. Latency drops dramatically. Scalability becomes almost trivial . It is also inherently secure. There are no runtime calls to sensitive APIs. No database queries during requests. Everything needed is already embedded in the generated output. The limitation is obvious. Static content does not change unless you rebuild. That constraint used to make static generation impractical for dynamic applications. Rebuilding an entire site for a small content change is inefficient and often unrealistic. Next.js softens that limitation through incremental static regeneration. Incremental regeneration changes the conversation Incremental static regeneration introduces a middle ground. You can generate a page once and then update it periodically without rebuilding the entire application. Instead of choosing between fully static and fully dynamic, you define how stale your data can be. A page might be regenerated every few minutes, hours, or based on traffic patterns. The first request after the revalidation window triggers a new render, and subsequent users receive the updated version . This model shifts the question from “Is this page static or dynamic?” to “How fresh does this data need to be?” Many applications do not require real-time updates. A slight delay is acceptable if it significantly improves performance and reduces load. ISR lets you capture that balance without overcommitting to server-side rendering. Rendering decisions are architectural decisions At an intermediate level, the real challenge is not understanding how each strategy works. It is knowing where to apply them. Rendering is tightly coupled with architecture. A marketing page benefits from static generation because it rarely changes and needs to load instantly. A product listing page might use a mix of static generation and periodic revalidation. A user dashboard leans toward client-side rendering for responsiveness. A checkout flow may rely on server-side rendering for security and consistency. These are not isolated decisions. They influence data flow, caching strategies, and even how teams structure their codebase. Rendering becomes part of the system design, not just a framework feature. The hidden cost of getting it wrong Misusing rendering strategies can degrade your application. Overusing server-side rendering can lead to unnecessary latency and higher infrastructure costs. The app works, but it feels slower than it should. Relying too heavily on client-side rendering can hurt first impressions. Users wait longer. Search engines struggle to index content. Performance scores drop. Using static generation without a plan for updates can create stale experiences. Content lags behind reality. Users notice. The tricky part is that each of these issues emerges gradually. They rarely show up in small projects or local environments. They appear under real traffic, real data, and real user expectations. That is why rendering decisions deserve more attention than they usually get. Hybrid rendering is not a feature but a mindset Next.js is often described as a hybrid framework. That description is accurate but incomplete. A single page can combine multiple approaches. The shell might be statically generated. Critical data might be fetched on the server. Interactive components might rely on client-side rendering. Each part of the page is treated independently based on its requirements. This is where the framework becomes powerful. You are not forced into a single pattern. You can optimize each piece of the experience. The challenge is maintaining clarity. Without clear boundaries, hybrid approaches can become messy. It is easy to lose track of where data is fetched, where rendering happens, and why certain decisions were made. What developers often miss At this stage, most developers understand the mechanics of SSR, CSR, and SSG. The gap is usually in decision-making. Three patterns tend to show up repeatedly. The first is defaulting to server-side rendering for anything dynamic. It feels safe, but it often introduces unnecessary overhead. The second is treating client-side rendering as a performance solution. It improves interactions but can hurt initial load and SEO. The third is underestimating static generation. Many pages that could be static end up being rendered dynamically simply because it feels easier. These patterns become problematic when applied without context. A more practical way to think about rendering Instead of categorizing pages by rendering type, it helps to evaluate them through a few simple questions: How often does this data change? Does this content need to be indexed by search engines? How important is initial load performance? Does this page require per-user personalization? What is the acceptable level of staleness? These questions lead you toward a more balanced approach. A page that rarely changes and needs strong SEO leans toward static generation. A page with user-specific data leans toward server or client rendering. A page with moderate updates fits well with incremental regeneration. Rendering stops being a technical choice and becomes a product decision. The real takeaway Rendering strategies are not competing features. They are tools with different costs and benefits. Next.js gives you the ability to combine them, but it does not make the decisions for you. That responsibility sits with the developer. When you align your rendering choices with the needs of your application, performance improves, infrastructure becomes more efficient, and the user experience feels intentional. When you do not, the issues show up slowly and compound over time. Rendering is one of those areas where small decisions have outsized impact. It is worth treating it as a first-class concern, not an afterthought. Enjoyed this piece? Take it further with Real-World Next.js by Michele Riva, and learn how to build scalable, high-performance modern web applications. Building with AI? We want to hear from you 👀 Take our quick survey and help us shape content around real workflow bottlenecks. Take the survey This Week in the News 🧠 Copilot now asks another AI before trusting itself:GitHub is experimenting with a “second opinion” system inside Copilot CLI. A separate model from a different family reviews the agent’s plan and code to catch blind spots and edge cases before execution. The bigger shift is architectural. AI tools are starting to validate each other, not just generate output. One model writes, another critiques, and the developer sits above both. 🔐 Cloudflare just moved up the deadline for quantum-safe internet: Cloudflare is accelerating its roadmap to make the entire platform post-quantum secure by 2029, including authentication, not just encryption. The urgency comes from recent breakthroughs suggesting current cryptography could be broken sooner than expected. The takeaway is simple. Quantum risk is no longer theoretical, and migration timelines are shrinking fast. ⚙️ Node.js keeps shipping under-the-radar improvements: Recent updates to the current release line focus on better memory control, improved test runner capabilities, and incremental API enhancements. Nothing headline grabbing, but that is the point. Node is doubling down on stability and developer ergonomics rather than chasing trends. 📦 A new way to explore npm without the CLI: Patak and Zeu introduced npmx, a fast, browser-based interface for exploring the npm ecosystem. It focuses on speed, discoverability, and a cleaner way to navigate packages without relying on terminal workflows. The idea is simple but interesting. As ecosystems grow, developer tooling is shifting from command-heavy interfaces to faster, more visual ways of understanding what’s out there. Beyond the Headlines ⚠️ How attackers are now targeting open source maintainers: Simon Willison breaks down a new wave of supply chain attacks that rely on social engineering, not exploits. Maintainers are being manipulated into merging malicious code through trust, urgency, and subtle persuasion. The pattern is uncomfortable but clear. The weakest point in the supply chain is no longer code. It is the human reviewing it. 🧩 Claude is not your software architect: This piece challenges a growing assumption that AI can design systems end-to-end. While models can generate code and suggest structures, they lack the context needed for real architectural decisions. The distinction matters. AI can assist implementation, but architecture still depends on trade-offs, constraints, and long-term thinking. 🔐 Your email is still easy to scrape: Email obfuscation techniques that used to work are now trivial to bypass. This article shows how bots extract addresses even from “protected” formats and what actually works today. The takeaway is practical. Security through obscurity is fading fast, especially when automation keeps improving. 🎥 The React hook most developers still ignore:useSyncExternalStore solves a specific but important problem: syncing external state with React without breaking consistency. It rarely shows up in everyday code, which is why many developers overlook it. But once you start working with shared state across systems, it becomes one of those tools that quietly fixes hard-to-debug issues. Stop Prompting. Start Speccing. After a hugely successful Cohort 1, we're back with the Cohort 2 hands-on workshop! Here you'll build a real full-stack application using the same spec-first methodology used by MAANG engineering teams. 20 early bird seats at 40% off. Use code NL40 Tool of the Week 🦴 Generate skeleton screens directly from your DOM Boneyard takes a snapshot of your existing DOMand automatically generates pixel-perfect skeleton screens. Instead of manually building placeholders, it mirrors your actual layout, saving time and keeping loading states visually consistent. It’s a small idea with a practical payoff, especially for apps where perceived performance matters as much as real speed. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
20 Apr 2026
11 min read
Save for later

WebDevPro #135: MCP Is Redefining How We Expose Backend Capabilities

Kinnari Chohan
20 Apr 2026
11 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #135 MCP Is Redefining How We Expose Backend Capabilities Crafting the Web: Tips, Tools, and Trends for Developers Lessons Learned from Security Incidents in Mobile Apps Join Security Researcher and Pentester,Jan Seredynski, onMay 12as he dissects real-world security incidents in banking, food delivery, and e-commerce. From face verification bypass to location spoofing, he'll break down the anatomy of a breach and what teams can do differently to address them. Register today! 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! For years, web development has revolved around a stable contract: define endpoints, shape responses, and let clients call into your system. That model still works, but it starts to feel strained when the client is no longer just a browser or a mobile app, but an AI system that needs to discover, interpret, and use your backend dynamically. Most AI integrations today are still improvised. Teams wire up function calling, patch together tool layers, and build thin wrappers around internal APIs. The result, therefore, is predictable: duplication, brittle integrations, and systems that are hard to extend. MCP, on the other hand, introduces a different approach. Instead of treating AI as an add-on, it defines a standard way to expose capabilities so clients can discover and use them reliably. This is not just an AI story. It is an architectural shift that web developers should understand early. Before we get into it, here’s this week at a glance: ⚛️ TanStack Start adds experimental React Server Components support 🤖 Claude introduces “routines” for repeatable workflows 🧩 GitHub introduces stacked PRs in private preview 🎨 Why AI still struggles with frontend development 🧹 Mastering ESLint rules for better code quality 🎤 Inside npmx, a new way to explore the npm ecosystem From Endpoints to Capabilities Traditional APIs are built around endpoints. You define routes, attach handlers, and document how clients should call them. MCP shifts the focus from endpoints to capabilities. Instead of exposing a set of URLs, you expose: Tools Resources Templates They form the core building blocks of MCP, separating computation, context, and reusable instruction patterns. Endpoints assume the client already knows what exists. Capabilities allow the client to discover what is available at runtime. That removes a lot of implicit coupling between systems. It also reduces the need to hardcode assumptions into every integration. A Familiar Model, Framed Differently At a high level, MCP follows a structure that will feel familiar: A server exposes functionality A client connects and invokes it A host provides the environment where interactions happen Communication happens through structured messages, with an explicit handshake where both sides exchange capabilities before doing any work. The initialize-initialized handshake ensures both client and server agree on capabilities before any interaction begins. For web developers, this is closer to: API discovery combined with schema introspection Contract negotiation before execution Runtime awareness instead of static assumptions The difference is that this model is designed for AI clients that interpret intent rather than strictly follow instructions. Why This Matters in Practice The biggest problem MCP addresses is not performance or scale. It is integration friction. Without a standard: Each AI integration defines its own schema Tool selection becomes unreliable Context handling becomes inconsistent Systems become harder to compose The source material highlights three recurring pain points: Fragmented context and limited windows Tool and memory integration complexity Lack of composability across domains MCP tackles these by standardizing how capabilities are described and invoked. For web developers, this is similar to the role OpenAPI played for REST. It does not replace APIs. It makes them easier to integrate and reason about. The Real Design Challenge: Clarity Over Cleverness One of the most practical insights from the material is how much naming and structure influence behavior. In MCP systems: Tool descriptions guide selection Parameter names influence argument extraction Return shapes affect how results are used Vague naming leads to incorrect tool calls. Overloaded schemas introduce ambiguity. Overly complex resources increase cost and reduce accuracy. This is not new, but the consequences are sharper. In traditional APIs, poor naming slows developers down. In AI-driven systems, it changes how the system behaves. That forces a shift toward: Clear, literal naming Minimal and explicit schemas Stable, predictable outputs This is API design discipline applied in a stricter environment. Transport, Deployment, and the Web Stack Another reason this matters for web developers is where these systems end up. Local development may start with simple transports, but production systems move toward HTTP-based communication, aligning with existing infrastructure. That brings familiar concerns back into play: Routing and endpoints Authentication and token validation Rate limiting and gateway policies Observability and request tracing The recommendation to move toward a single HTTP endpoint for MCP traffic reflects a clear alignment with modern backend practices. It is an extension of the web stack and not just parallel ecosystem. Authentication Is No Longer Per Request One subtle but important shift is how authentication is handled. Instead of validating each request independently, MCP systems often establish trust at the connection or session level, with tokens validated before interaction begins. That changes how you think about: Access control Role mapping Scope enforcement It also pushes developers to consider: Which tools should be publicly accessible Which require elevated permissions How to isolate sensitive operations This is closer to service-level authorization than traditional request-level checks. From Local Tooling to Production Systems One of the strengths of the MCP approach is how it spans environments. The same server can: Run locally for testing Be inspected through development tools Be integrated into host environments Be deployed over HTTP for production use This continuity reduces the gap between experimentation and deployment. For web developers, that means fewer environment-specific rewrites, easier validation workflows, and more predictable rollout paths It also makes it easier to introduce AI-driven capabilities incrementally instead of rewriting entire systems. Where MCP Fits in a Web Developer’s Mental Model It helps to place MCP alongside existing concepts rather than treating it as something entirely new. Think of it as: An extension of API design for AI-native clients A structured way to expose backend capabilities A protocol for discovery, not just execution It does not replace REST or GraphQL. It complements them by sitting one layer above, where interpretation and routing happen. That positioning makes it easier to adopt without overhauling your architecture. Key Takeaways MCP reframes backend design from static endpoints to discoverable capabilities Tools, resources, and prompts map cleanly to actions, context, and reusable logic Clear naming and schema design directly influence system behavior HTTP-based deployment keeps MCP aligned with existing web infrastructure Authentication shifts toward session-level trust and scoped permissions The protocol reduces integration friction by standardizing how capabilities are exposed MCP is still early, but the direction is clear. AI clients are becoming another consumer of backend systems, and they require more than just endpoints. For web developers, this is less about learning a new tool and more about recognizing a familiar pattern evolving into something more dynamic. If you want to explore these ideas in more depth, check out Ship an MCP Server in Python FAST by Christoffer Noring. Building with AI? We want to hear from you 👀 Take our quick survey and help us shape content around real workflow bottlenecks. Take the survey This Week in the News ⚛️ TanStack Start adds experimental React Server Components support:TanStack Start now includes experimental support for React Server Components (RSC), bringing server-first patterns into its full-stack React framework. It’s another sign that RSC is slowly moving from theory into real-world tooling. 🤖 Claude introduces “routines” for repeatable workflows:Claude now supports routines, a way to define repeatable workflows for common tasks. Instead of prompting from scratch each time, developers can structure and reuse sequences, making interactions more consistent and efficient. 🎨 Hugo adds new CSS capabilities:Hugo’s latest updates introduce new CSS-related capabilities, expanding how styles can be handled within the static site generator. The changes aim to simplify styling workflows and give developers more flexibility when working with modern CSS setups. 🧩 GitHub introduces stacked PRs in private preview:GitHub has opened a private preview for stacked pull requests, adding native support for workflows where large changes are split into a chain of dependent PRs. This approach can make reviews more manageable and help teams ship complex features incrementally. Beyond the Headlines 🎨 Why AI still struggles with frontend development: While AI tools are getting better at generating code, frontend development remains a weak spot. This piece breaks down where things fall apart, especially around layout, styling, and the nuanced decisions that go into building good user interfaces. The bigger takeaway is that frontend work isn’t just about producing code. It involves visual judgment, edge cases, and context that are still hard for AI systems to consistently handle. 🏗️ Rethinking architecture with the vertical codebase: This article explores the idea of a “vertical codebase,” where features are organized end-to-end instead of being split across layers like components, services, and utilities. The approach focuses on grouping everything related to a feature in one place, making codebases easier to navigate and reason about. It’s a shift from traditional horizontal layering toward a structure that prioritizes ownership, clarity, and maintainability as applications grow. 🧹 Mastering ESLint rules for better code quality: This guide takes a deeper look at how to effectively use ESLint rules beyond basic setups. It focuses on understanding, customizing, and applying rules in a way that actually improves code quality instead of just adding noise. The key idea is that linting works best when it reflects team conventions and real-world usage, not just default configurations. 📦 Ten years of JavaScript modules on the web: It’s been ten years since the initial push to bring native JavaScript modules to the web platform. This retrospective looks at how far the ecosystem has come, from early proposals to widespread adoption across browsers and tooling. It’s a reminder of how foundational features evolve slowly, but end up reshaping how we structure and ship web applications. 🎤 Inside npmx, a new way to explore the npm ecosystem:This 50-minute conversation with the developers behind npmx dives into the thinking behind a new way to browse and interact with the npm registry. The project is gaining traction as an alternative approach to discovering and working with packages. It offers a closer look at how tooling around npm is evolving beyond installation into better exploration and developer workflows. Stop Prompting. Start Speccing. After a hugely successful Cohort 1, we're back with the Cohort 2 hands-on workshop! Here you'll build a real full-stack application using the same spec-first methodology used by MAANG engineering teams. 20 early bird seats at 40% off. Use code NL40 Tool of the Week 🎨 Extract color palettes directly from images Choosing the right color palette can be tricky, especially when working with images. Color Thief makes it simple by extracting dominant colors and palettes directly from images using a lightweight JavaScript library. It’s especially useful for building dynamic UIs, theming applications, or generating color schemes based on user content. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0
Kinnari Chohan
22 Apr 2026
12 min read
Save for later

WebDevPro #136: Agent Continuous Learning Framework: Build Traces, Refine Context, Iterate with a Harness (like DeerFlow), then Fine Tune the Model

Kinnari Chohan
22 Apr 2026
12 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #136 Agent Continuous Learning Framework: Build Traces, Refine Context, Iterate with a Harness (like DeerFlow), then Fine‑Tune the Model By Daniel He, Co‑Author of DeerFlow Hi , Welcome to this week’s bonus issue of WebDevPro! DeerFlow is an open‑source SuperAgent framework based on LangGraph, focusing on multi‑agent orchestration, with 60K+ stars on GitHub. Core contributor Daniel He has long been deeply involved in agent workflow design and stateful graph execution, and he is dedicated to pushing autonomous agents to the true limits of production environments. This article is a speaker feature for an upcoming FREE live session hosted by Packt, where the DeerFlow team will walk through how these ideas translate into real systems. Build AI Agents with DeerFlow 2.0 Expect live demos, including agents that review pull requests and generate full research reports from a single prompt, along with a closer look at how DeerFlow coordinates models like GPT, Gemini, and DeepSeek behind the scenes. The event is designed for engineers, AI practitioners, product teams, and anyone exploring autonomous workflows or open source agent systems. It also includes insights from the maintainers on how the project evolved from DeerFlow 1.0 to 2.0, what is coming next, and how to get involved. 📅 Wednesday, May 6 • 9 AM – 10:30 AM EDT 🌐 Free online event by Packt Publishing Register now How exactly can an agent system "continuously become stronger"? It relies on training data, training infrastructure, and evaluation loops, which makes this kind of continuous improvement a platform-level capability rather than something a typical product team can iterate on frequently. Recently, Harrison Chase, the founder of LangChain, posted an X thread that breaks down the Agent Continuous Learning system into three layers: Model (model weights), Harness (execution mechanism), and Context (configurable memory). He then combined this with cutting-edge work such as Meta-Harness and LangChain Deep Agents to analyze the learning methods, implementation costs, and applicable scenarios of each layer. Based on this analysis, a possible action path for product teams is: first, get Traces right, then do Context learning, then establish a Harness optimization loop, and finally consider model fine-tuning. The core argument of this article is very clear: For AI agents, "continuous learning" should not be understood as merely updating model weights. An agent system can actually evolve continuously on three levels: Model, Harness, and Context. Core Framework: Three-Layer Agent Learning In his thread, Harrison broke down the agentic system into three layers: Model: The underlying model itself, which is the weights. Harness: The "shell" that drives the model's operation, including agent code, fixed tools, fixed hints, execution loops, etc. Context: A configurable context located outside of the harness, such as memory files, skills, user configurations, and team configurations. The value of this definition lies in the fact that it expands "learning" from a single model training problem into a complete systems engineering problem. Layer-by-layer interpretation 1. Model layer: The most traditional, but also the most complex layer. This layer corresponds to continuous learning, which is most familiar to everyone: Update weights usingmethods such as SFT and RL. It is also possible to use a more fine-grained adaptation method, such asLoRA. The goal is to make the model perform better on new tasks. However, an old problem persists: catastrophic forgetting. That is, after the model learns new things, its old abilities actually degenerate. Daniel’s judgment is: For most teams, the Modellayer has the highest continuous learning cost. It relies on training data, training infrastructure, evaluation loop, and model deployment mechanism. This is more like a platform-level capability than a routine method that a typical product team can frequently iterate upon. So while Harrison acknowledges the importance of the model layer, his real focus is not there. 2. Harness Layer: The most underestimated leverage point in agent engineering over the past year. “Harness” does not refer to the model itself, but rather "how the model is used": How to write system prompts How to expose tools to the model How to organize loop calls When to truncate the context, when to retry, and when to determine if the task is complete Which logs and traces are saved for later analysis Harrison specifically mentions the work at https://yoonholee.com/meta-harness/ in his article. Its main idea can be summarized as follows: Run the agent on a batch of tasks. Collect complete execution logs and scores. Store these historical candidates, source code, and execution traces in the filesystem. Then,have a coding agent read these materials and propose new harness modifications. Evaluating the new harness and continuing its iterations. This is important because it illustrates: Agent evolution can be achieved without changing the model, only the runtime framework. The truly high-value optimization targets are often the "execution mechanism" rather than the "parameters". The more complete the traces, the more harness optimization resembles an engineering iteration rather than a matter of guesswork and prompt adjustments. The official Meta-Harness page also presents strong results: it emphasizes its key difference by allowing the optimizer to see the complete historical code, scores, and execution traces, rather than just a summary. The authors claim that this "filesystem-level context" can increase the available diagnostic information for each round of optimization by an order of magnitude compared to traditional methods. 3. Context Layer: Closest to the business logic, and best suited for initial implementation Harrisondefines Context as content located outside of the harness used to configure the agent, for example: Instructions Skills Tools Memory files User preferences Team rules The key to this layer is not "what the model has learned", but "what the system has remembered and how to continue using it in subsequent sessions". The official documentation for LangChain Deep Agents explains this in great detail. It supports: Agent-scoped memory: All users share the same agent memory. User-scoped memory: Each user has their own independent memory. Online update: Write directly to memory during session Backend organization: Performing consolidation outside the session Skills are a form of procedural memory, loaded only when needed. This shows that the Context layer is not as simple as "adding more prompts", but a persistent, hierarchical, readable, writable, and searchable memory system. Two examples from the author Harrison used two mappings in the article, first for Claude Code: Models: Claude Sonnet, and so on Harness: Claude Code itself User context: CLAUDE.md, /skills, mcp.json This breakdown is very practical because it directly illustrates: Your perception that "Claude Code has become smarter" may not necessarily be due to changes in the model weights; It's also possible that the harness has been changed; Or perhaps you've configured its context better; And again for OpenClaw: Model: Can accept multiple models Harness: Pi (powers OpenClaw) plus some running scaffolding Agent context: SOUL.md and skills from ClawHub OpenClaw's public documentation says: SOUL.md is officially defined as the personality and tone configuration file for the agent ClawHubis defined as a public registry of OpenClaw skills/plugins This precisely confirms Harrison's point: the "continuous learning" of many agent systems essentially occurs at the configurable context layer, rather than the model fine-tuning layer. The paradigm shift this article aims to promote To summarize this article into a single sentence: An agent's continuous learning is shifting from "training the model" to "optimizing the entire system". There are at least three changes here: 1. The learning objective shifts from weights to system behavior. Traditional LLM continuous learning focuses more on: Has the loss decreased? Has the benchmark improved? Agent continuous learning focuses more on: Are they better at using tools? Are they better at planning steps? Can we improve our execution strategies by learning from past failures? Do we have a better understanding of the preferences of current users, teams, or organizations? 2. The learning unit is shifting from a single model to a hierarchical architecture. Within the same agent system, the learning frequency and cost of different layers are completely different: Model: Low frequency, high cost, platform level Harness: Mid-frequency, engineering-driven, measurable Context: High frequency, business-driven, most likely to occur online This means that continuous learning should not have just one master switch, but rather three different mechanisms. 3. Traces become a unified fuel. Harrison repeatedly emphasizes "traces" at the end of the article. This is one of the most crucial infrastructure assessments in the entire text. The reason is straightforward: To modify the model, you need traces as a source of training/preference data To improve harnessing, you need traces as diagnostic material for failures To modify the context, you need traces as source material for experience extraction In other words, without high-quality traces, there is no high-quality agent learning loop. Some personal judgments 1. The Context layer will be the first to become widespread. Daniel believes that the Context layer, rather thanthe Modellayer, will be the first of the three layers to be widely implemented. Reasons: No training required The most direct impact on business returns Isolation can be done by user / team / org Easy to manage permissions and rollback Easier to meet the controllability requirements of enterprise systems Many capabilities that are packaged today as "the agent can remember" essentially belong to this level. 2. The Harness layer will become the focus of the next round of agent infrastructure competition. If 2024 was mainly about who could get their agent up and running first, then 2026 will be more about: Whose harness is more stable? Whose traces are more complete? Whose evaluation and replay system is more closed-loop? Who can quickly incorporate failure cases into the next version of agent behavior improvement? This is whywork like Meta-Harness is worth paying attention to. It represents a very engineering-oriented approach: letting the agent help you change the agent. Practical reflections on your team's development of agent products If your team is planning to incorporate "continuous learning" into its agent roadmap, Daniel recommend proceeding in the following order: Phase 1: First, get the traces right Unified recording of task input, tool calls, key intermediate states, output results, and human feedback Preserve reviewable evidence for failed tasks, rather than just reporting an error Add user/org/task/version dimension tags to traces Phase 2: Prioritize context learning Start with user preferences, team rules, glossary, and standard operating procedures (SOPs) Distinguish between read-only memory and writable memory Define the scope: which is user-level and which is org-level Support both online writing and offline defragmentation update paths Phase 3: Establish the Harness optimization loop Run the agent continuously on a standard task set Perform A/B testing and automated evaluation on the harness version The coding agent reads traces to help propose candidates for harness modification Establish a rollback mechanism to prevent situations where changing the prompt might seem convenient in the short term, but ultimately leads to decreased overall stability Phase 4: Consider model-level learning only as a last resort Only when you have accumulated enough high-quality traces Furthermore, optimizations at the harness/context layer are nearing their limits A reusable decision framework When faced with the question "How should an agent undergo learning?", you can first ask four questions: What needs to be changed this time? Is it the model capabilities, the operating mechanism, or the configurable memory? Should this change apply to theagent, user, or org scope? Should thisupdate occur immediately during runtime, or should it be processed in an offline task before taking effect? Arethere enough complete traces to support an assessment of whether this learning was truly effective? If these four questions cannot be answered clearly, the so-called "continuous learning" is most likely just a vague slogan. Conclusion The value of Harrison Chase's post and accompanying article lies not in proposing a completely new algorithm, but in breaking down agent continuous learning into a more practical three-layer framework: Model learning addresses underlying capabilities Harness learning addresses the execution mechanism Context learning addresses memory and personalization The two things that the product team should act on immediately are not training the model, but rather: Build the infrastructure for traces Productize the context and harness learning loop This is also Daniel’s core conclusion for this article: stronger agents in the future will not necessarily come from larger models, but are more likely to come from systems that are better at "reviewing, remembering, and reconstructing". Want to know more about how to turn your prompts into workflows with an agent harness? Join us for a live demonstration of DeerFlow 2.0 on 6th May! Register now! That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0

Kinnari Chohan
27 Apr 2026
14 min read
Save for later

WebDevPro #137: Why Blocking Code Breaks Node.js Performance

Kinnari Chohan
27 Apr 2026
14 min read
Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #137 Why Blocking Code Breaks Node.js Performance Crafting the Web: Tips, Tools, and Trends for Developers Catch the latest HubSpot Developer Platform updates in Spring Spotlight Spring Spotlight 2026 is live and we’ve rounded up the top updates for developers. See what's new for the HubSpot Developer Platform! Ship faster with AI coding tools like Cursor, Claude Code, and Codex. Build MCP-powered AI connectors, run serverless functions with support for UI extensions, and use date-based versioning to streamline roadmap planning. Explore Now 📢 Important: WebDevPro is Moving to Substack We’ll be moving WebDevPro to Substack soon.Once the transition is complete, all future issues will come from packtwebdevpro@substack.com. To make sure the newsletter continues reaching your inbox, please add this address to your contacts or whitelist it in your mail client. No other action is needed. You’ll keep receiving WebDevPro on the same weekly schedule. Substack will also give you more control over your subscription preferences if you decide to adjust them later. Welcome to this week’s issue of WebDevPro! Node.js is often chosen for its ability to handle concurrent workloads efficiently. Its event-driven, non-blocking architecture allows applications to process multiple operations without waiting for each one to complete. This model is particularly effective in I/O-heavy systems where responsiveness matters more than sequential execution. Because of this, there is a widespread assumption that Node.js applications will perform well under load by default. In practice, that assumption depends on one critical condition: the application must remain non-blocking.Once blocking behavior is introduced, the system begins to behave very differently. Blocking code does not always cause immediate failures. In developmentenvironments,where concurrency is limited, the system may appear stable. Requestscomplete,responses are returned, and nothing seems obviously wrong. This creates a false sense of confidence in how the application will behave in production. Under real-world conditions, however, multiple operations occur simultaneously, and the event loop becomes a shared dependency across all of them. At that point, the cost of blocking becomes visible. Delays accumulate, responsiveness drops, and the system begins to struggle under load. The issue is not simply that blocking code exists, but how it interacts with the event loop and how that interaction scales. Before we get into it, here’s this week at a glance: ⚛️ TanStack Start adds experimental React Server Components support 🤖 Claude introduces “routines” for repeatable workflows 🧩 GitHub introduces stacked PRs in private preview 🎨 Why AI still struggles with frontend development 🧹 Mastering ESLint rules for better code quality 🎤 Inside npmx, a new way to explore the npm ecosystem What blocking means in practice In Node.js, all JavaScript execution happens on a single thread, coordinated by the event loop. The event loop continuously processes tasks by retrieving them from a queue, executing them, and delegating work when possible so that execution can continue without interruption. This model works efficiently because it assumes that tasks will be short-lived and non-blocking. Blocking code violates this assumption. When a blocking operation is executed, the event loop cannotproceedto the next task until the current one completes. This does not simply delay a single operation. It prevents all other pending tasks from executing during that time, effectively pausing the system’s ability to make progress. The impact is broader than it first appears. Callbacks that are ready to run remain in the queue. Timers do not fire at expected intervals. Incoming requests must wait before they can even begin execution. What appears to be a local delay becomes a system-wide bottleneck. This shared delay is what makes blocking code particularly problematic in Node.js. In systems with multiple threads, delays can be absorbed or distributed. In Node.js, the delay is centralized. A single blocking operation affects everything else that depends on the event loop. Why synchronous APIs are risky in Node.js Synchronous APIs are often attractive because they simplifyreasoning aboutcode. Execution flows in a straight line, making it easier to follow and debug. This simplicity can be beneficial in scripts or isolated tasks where concurrency is not a concern. In an event-driven system like Node.js, however, synchronous APIs introduce a significant limitation. Because theyexecute onthe main thread, they block the event loop until they complete. During this time, no other operations can be processed, regardless of how unrelated they may be. This creates a disconnect between how the code appears and how it behaves under load. The code may look efficient and predictable, but its execution forces all operations into a sequential pattern. Instead of handling multiple tasks concurrently, the system processes them one at a time. The result is reduced flexibility in how work is handled. As more operations depend on the event loop, the impact of synchronous APIs becomes more pronounced. What begins asa simple designchoice can evolve into a system-wide constraint. CPU-intensive work and hidden blocking Blocking behavior is not limited to I/O operations. CPU-intensive tasks can have an even greater impact, particularly when executed synchronously. These operations consume the main thread for their duration, preventing the event loop from processing other tasks. The book illustrates this with a cryptographic key generation example, where a synchronous function is executed repeatedly. Each iteration runs on the main thread, and the cumulative effect is a prolonged period during which theeventloop is unavailable. performance.mark("start-sync"); for (leti= 0;i< 10000;i++) { generateKeyPairSync("rsa", { modulusLength: 1024, }); } performance.mark("end-sync"); performance.measure("generateKeyPairSync", "start-sync", "end-sync"); In this scenario, the cost is not just the execution time of a single operation, but the accumulation of blocking across many iterations. The event loopremainsoccupied for the entire duration, preventing any other work from progressing. performance.mark("start-async"); for (leti= 0;i< 10000;i++) { generateKeyPair("rsa", {modulusLength: 1024 }, () => {}); } performance.mark("end-async"); performance.measure("generateKeyPair", "start-async", "end-async"); The asynchronous version changes how the work is executed. Instead of occupying the main thread, the operations are delegated, allowing the eventloop toremain available. This difference has a direct impact on system responsiveness, especially under load. The performance gap is not theoretical The difference between synchronous and asynchronous execution is not just conceptual. The bookdemonstratesthat the impact can be measured andobservedin practice. The synchronous version of the operation takes significantly longer and blocks execution entirely. The asynchronous version, by contrast, allows the system to continue processing other tasks while the work is being performed. This leads to betterutilizationof system resources and improved responsiveness. This distinction changes how performance should be evaluated. In Node.js, performance is not only about how quickly a single operation completes. It is about whether the system can continue to process other work during that time. A fast operation that blocks the event loop can still degrade system performance if it prevents other tasks from progressing. Conversely, a slower operation that does not block may have less overall impact on system responsiveness. Why this matters more under load Blocking behavior becomes more problematic as concurrency increases. In low-load environments, tasks are processed with minimal overlap, and delays may not be noticeable. The system appears stable because there is little competition for the event loop. As the number of concurrent operations grows, the situation changes. Multiple tasks begin to depend on the event loop at the same time. Each task expects to be processedin a timely manner, and blocking operationsdisruptthis expectation. When a blocking operation runs, it prevents the event loop from servicing other tasks. These tasks begin to accumulate in the queue, increasing waittimesand reducing overall throughput. The system becomes less responsive as more work is added. This is why blocking issues often appear only under load. The system needs sufficient concurrency for the delays to become visible. Once that threshold is reached, the impact becomes more pronounced, and performance begins to degrade more rapidly. Async wrappers do not remove blocking A common misconception is that using asynchronous syntax automatically makes an operation non-blocking. Wrapping a function in async/await or a callback does not change how the underlying work is executed. The book emphasizes that async syntax controls how results are handled, not how the work itself is performed. If the underlying operation is synchronous and CPU-intensive, it will still block the event loop. This distinction highlights the difference between code structure and execution behavior. A function may appear asynchronous in form while still behaving in a blocking manner in practice. Understanding this difference is essential foridentifyingperformance issues. Changing syntax without addressing the underlying execution does not resolve the problem. Reducing work instead of changing execution In some cases, improving performance is not about changing how an operation executes, but reducing how often it runs. The bookdemonstratesthis with an example where repeated computation is replaced with a caching approach. Instead of recalculating an expensive result for every request, the system stores the result and updates it periodically. This reduces the number of times the operation is executed, lowering the load on the event loop. letcachedSignature= null; const app =Express(); constsigningMiddleware= (_req, res, next) => { if (!cachedSignature) { console.info("Signature is not cached"); cachedSignature=signData(Date.now().toString()); setInterval( () => (cachedSignature=signData(Date.now().toString())), 10000); } res.setHeader( "X-Signature", `data=${cachedSignature.data.toString()};kid=${cachedSignature.keyId};sha512=${cachedSignature.signature}`); next(); }; This approach shifts the focus from execution style to execution frequency. By reducing repeated work, the system becomes more efficient without changing how the operation itself is implemented. The real trade-off Blocking code is often easier to write and reasonabout. Its linear execution model provides clarity and predictability, making it attractive in many situations. However, this simplicity comes at a cost in systems that rely on concurrency. Blocking operations limit the ability of the event loop to process multiple tasks efficiently, reducing overall system responsiveness. The trade-off is not simply between synchronous and asynchronous code. It is between local simplicity and system-wide performance. A piece of code that is easy to understand in isolation may introduce constraints that affect the entire application. As systems scale and concurrencyincreases, this trade-off becomes more significant. Decisions that seem minor at the code level can havesubstantialimpactatthe system level. Key Takeaways Blocking code prevents the event loop from processing other tasks, creating system-wide delays. Synchronous APIs introduce constraints that limit concurrency. CPU-intensive operations can block execution even when usedwith asyncsyntax. Performance in Node.js depends onmaintainingevent loop availability. Reducing execution frequency can improve efficiency without changingexecutionstyle. Final Thought Node.js relies on a responsive event loop to handle concurrent operations effectively. Blocking code disrupts this model by introducing delays that affect all tasks, not just the one being executed. The impact of blocking behavior is not always immediate, but it becomes clear under load. As concurrency increases, the system’s ability to process work efficiently depends on keeping the event loop free. Ifyou’dlike to read more on this, Node.js Design Patterns explores these ideas in depth, especially around asynchronous behavior, performance, and the architectural decisions behind scalable Node.js systems. This Week in the News 🧠 Tokenmaxxing is the new productivity metric developers are gaming:There’s a new habit showing up in AI-heavy workflows. Teams are starting to track and even optimize for token usage, treating it as a signal of productivity. Gergely Orosz digs into why that framing breaks down quickly. More tokens don’t mean better outcomes, just more input. It’s the same pattern developers have seen before with lines of code and story points, now resurfacing in an AI-shaped form. What’s interesting here is not the trend itself, but how quickly it appeared. Even with new tools, the instinct to measure the wrong thing hasn’t changed. ⚡ TypeScript 7.0 goes 10x faster with a Go rewrite: The TypeScript team has released the 7.0 beta after spending the past year porting the entire compiler to Go. The result is a version that’s roughly 10x faster than TypeScript 6.0, while keeping the type-checking behavior structurally the same. For developers, that means no major migration or new errors to worry about. Just significantly faster builds out of the box. 🟢 Node.js moves toward Temporal API and stabilizes key features:Node.js is preparing to support the Temporal API by default, likely landing in the upcoming v26 release. This brings a modern, more reliable alternative to JavaScript’s existing Date handling into the runtime. At the same time, Node.js 24.15.0 (LTS) marks require(esm), and the module compile cache as stable, and introduces a new --max-heap-size flag. Together, these updates signal continued progress in both runtime capabilities and performance tooling. 📧 React Email 6 simplifies a fragmented ecosystem:React Email 6 introduces a major update focused on cleaning up versioning issues across its ecosystem. The release makes it easier to manage dependencies and ensures the CLI and components stay in sync. For teams building email templates with React, this should reduce friction and make the overall workflow more predictable. Beyond the Headlines ⚡ A new Angular compiler built on Oxc:This post explores an experimental Angular compiler powered byOxc, a Rust-based toolchain focused on performance. The goal is to significantly speed up builds and modernize the compilation pipeline. It’sanother signal that JavaScript tooling is steadily moving toward Rust-based infrastructure to push performance boundaries. 🚨 Investigating fake stars in GitHub repos: This investigation looks into how some GitHub repositories inflate their popularity using fake stars. It highlights how easily perception can be manipulated and why star counts aren’t always a reliable signal of quality. For developers,it’sa reminder to evaluate projects based on code, activity, and community, not just metrics. 🎥 Rethinking modern web architecture: This talk dives into how modern web architecture is evolving, covering trade-offs in performance, complexity, and developer experience. It offers a broader perspective on how current patterns scale in real-world applications. A useful watch ifyou’rethinking beyond frameworks and into long-term system design. 🧩 Features worth borrowing from npmx: This article breaks down specific ideas from the npmx project that could inspire better developer tools. It highlights practical features that improve how developers explore and interact with packages. The takeaway is simple. Good tooling often comes from rethinking small UX details. 🛠️ Why app stability matters more than ever:Cursor shares insights into building stable applications, especially in environments where rapid iteration and AI-assisted development are becoming the norm. The focus is on reducing breakage and maintaining reliability as systems evolve. It’sa reminder that speed is valuable, but stability is what keeps users and teams productive. Tool of the Week 🎬 Add unique animations to your React apps Building engaging UIs often means going beyond basic transitions.Animataoffers a collection of 100+ animation-focused React components, including effects like animated beams, spreading cards, and even a Slack-style intro screen. It’sa handy resource if you want to add more personality to your UI without building complex animations from scratch. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;display:none;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}.social_block .social-table{display:inline-block!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more
  • 0
  • 0
Modal Close icon
Modal Close icon