[{"content":"Every request your app serves is a chance to do less work. Caching is just the art of not redoing work you already did. Sounds simple. It isn\u0026rsquo;t — there\u0026rsquo;s a whole stack of places you can stash data, each with its own rules, and picking the wrong layer (or the wrong expiry) costs you either stale bugs or a slow site.\nI\u0026rsquo;ve spent enough hours staring at a page that \u0026ldquo;should have updated\u0026rdquo; but didn\u0026rsquo;t, only to realise the answer was cached three layers deep. So let me walk through every place you can cache — from the user\u0026rsquo;s browser all the way down to compiled bytecode on your server — and where each one bites you.\nThe mental model: caching is layered, top to bottom Here\u0026rsquo;s the thing most tutorials skip. A single HTTP request passes through many caches before it ever touches your database. Each layer is closer to the user (faster, cheaper) or closer to the origin (fresher, more correct). Your job is to push work as far \u0026ldquo;up\u0026rdquo; toward the user as you safely can.\nThe higher a cache hit, the better. A hit in the browser costs zero network. A hit at the CDN costs one short network hop and never wakes your server. A hit in Redis still runs your app but skips the database. You get the idea.\nLet\u0026rsquo;s go through them.\nBrowser caching (the free one you\u0026rsquo;re probably wasting) The browser cache is local storage on the user\u0026rsquo;s own device, and a cache hit there has zero network cost [1]. It\u0026rsquo;s the cheapest win in web performance and also the one people configure worst.\nEverything here is controlled by the response headers you send. The star of the show is Cache-Control. A few directives you actually need:\nmax-age=N — the response stays fresh for N seconds without contacting the server. max-age=3600 means \u0026ldquo;reuse this for an hour, don\u0026rsquo;t even ask\u0026rdquo; [2]. no-cache — badly named. It doesn\u0026rsquo;t mean \u0026ldquo;don\u0026rsquo;t cache.\u0026rdquo; It means \u0026ldquo;cache it, but revalidate with the server before using it.\u0026rdquo; no-store — this is the real \u0026ldquo;don\u0026rsquo;t cache anything\u0026rdquo; for sensitive data. private vs public — private means only the user\u0026rsquo;s browser may store it (not shared proxies); public means intermediary caches can too. immutable — tells the browser this file will literally never change, so don\u0026rsquo;t even bother revalidating. Validation: ETag and 304s What happens when max-age expires? The browser doesn\u0026rsquo;t blindly re-download. It revalidates. This is where ETag comes in — an arbitrary version token the server generates for a resource [2]. When the cached copy goes stale, the browser sends the token back in an If-None-Match header, and if nothing changed the server replies 304 Not Modified with an empty body [2]. You skip the download entirely and just refresh the freshness clock.\nSo Cache-Control decides whether to revalidate, and ETag decides whether the bytes actually changed. They\u0026rsquo;re a team.\nThe stale-while-revalidate trick There\u0026rsquo;s a beautiful directive called stale-while-revalidate. It lets a cache serve a stale response immediately while it fetches a fresh copy in the background [2]. The user gets an instant response; the next user gets the updated one. For things like avatars or article lists that can be a few seconds out of date, this is the sweet spot — fast and eventually fresh.\nHere\u0026rsquo;s a sane header for a fingerprinted asset that never changes:\nCache-Control: public, max-age=31536000, immutable And for an HTML page that changes but should feel fast:\nCache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=300 Honestly, most \u0026ldquo;why won\u0026rsquo;t my CSS update?!\u0026rdquo; problems trace back to someone slapping a year-long max-age on a file whose name never changes. Which brings us to the trick that makes aggressive caching safe.\nCache busting with fingerprints You can cache a file for a full year and still ship updates instantly if the filename changes whenever the content does. This is called content fingerprinting — a hash of the bytes goes into the filename, so app.a1b2c3.js becomes app.d4e5f6.js the moment a single byte changes [7]. New bytes, new URL, new cache entry. Old URL just\u0026hellip; stops being referenced.\nEvery modern bundler (Webpack, Vite, Rollup, Parcel) does this for you and writes a manifest so your HTML points at the right hashed name [7]. Set max-age=31536000, immutable on anything fingerprinted and you\u0026rsquo;ll hit 90%+ cache ratios without fear [7].\nService workers and the Cache API (offline-grade caching) The browser\u0026rsquo;s HTTP cache is passive — you set headers and hope. A service worker is active. It\u0026rsquo;s a script that sits between your app and the network like a programmable proxy, intercepting every fetch and deciding how to answer: from cache, from network, or both [3].\nThis is the backbone of Progressive Web Apps and offline support. The three tools you combine are the Fetch API, the Service Worker API, and the Cache API [3]. And there are a handful of named strategies you\u0026rsquo;ll reach for over and over:\nStrategy How it works Good for Cache-first Check cache; only hit network on a miss App shell — HTML, core CSS/JS, logo Network-first Try network; fall back to cache if offline Live data — feeds, messages Stale-while-revalidate Serve cache now, refresh in background Avatars, lists that tolerate slight staleness Cache-only Never touch the network Precached static assets Network-only Never cache Non-idempotent POSTs, payments The service worker usually populates the cache in its install handler (precaching the shell) or lazily in the fetch handler [3]. One non-negotiable detail: service workers only run over HTTPS — because they can intercept all your traffic, browsers refuse to run them on insecure origins [3]. Localhost is the only exception.\nThe thing that trips people up: a service worker doesn\u0026rsquo;t respect your HTTP cache headers. It\u0026rsquo;s a separate cache with its own logic. So now you\u0026rsquo;ve got two browser-side caches to reason about. Powerful, but the surprise factor is real, which is why folks push for deterministic caching — versioned cache names, explicit cleanup — so you\u0026rsquo;re never guessing which copy a user has.\nCDN and edge caching (the global shortcut) A Content Delivery Network is a fleet of servers scattered around the world — Cloudflare, Fastly, CloudFront and friends — that sit between your users and your origin. When content is cached at an edge location (a \u0026ldquo;POP\u0026rdquo;) near the user, it\u0026rsquo;s served from there instead of making the round trip to your server [1]. Two wins at once: lower latency for the user, and dramatically less load on your origin.\nThe metric that matters here is cache hit ratio — the fraction of requests the edge answers without bothering your origin. Aim for 90%+ on static content [5]. Every percentage point is real money and real latency saved.\nA few things that quietly wreck your hit ratio:\nQuery string chaos. By default many CDNs treat ?utm_source=twitter as a different URL from ?utm_source=email, fragmenting your cache. Strip or whitelist query params — cache on ?page= but ignore tracking junk [5]. Cold edges after deploy. A freshly purged edge has to refill, so testing right after a deploy hides whether your steady-state policy is actually healthy [7]. One-size-fits-all TTLs. Static, fingerprinted files can live at the edge for 365 days; dynamic-but-cacheable HTML should get something modest like 5–10 minutes [5]. CDNs use Cache-Control too, but pay attention to s-maxage — it\u0026rsquo;s the max-age specifically for shared caches like CDNs, letting you tell the edge \u0026ldquo;cache for 60s\u0026rdquo; while telling browsers something different. When you deploy, you either wait out the TTL or issue a purge to evict content immediately, which is worth wiring into your CI/CD pipeline so releases and cache-busting happen together [7].\nSome providers go further — Cloudflare\u0026rsquo;s tiered caching predicts content surges, and CloudFront\u0026rsquo;s Origin Shield adds a consolidating layer so your origin sees even fewer requests [5]. Edge is also increasingly where you run code, not just cache it, but that\u0026rsquo;s a rabbit hole for another day.\nReverse proxy caching (full-page caching on your own turf) Before a request reaches your application code, you can park a reverse proxy in front of it that caches entire HTTP responses. Varnish is the classic example — a caching HTTP reverse proxy that sits between your app and the world [8].\nHere\u0026rsquo;s why it\u0026rsquo;s so effective. Varnish caches the final rendered HTML after all your PHP (or whatever) has run. When it serves a page from cache, it doesn\u0026rsquo;t run your code at all — which means no database queries, no backend calls, no third-party requests for that page [8]. The request comes in, Varnish answers from RAM, done. Requests that would\u0026rsquo;ve hammered your database just\u0026hellip; don\u0026rsquo;t.\nThe flow is dead simple:\nRequest arrives at Varnish. Varnish tries to answer from cache. On a miss, it forwards to your backend, fetches the response, stores it, and delivers it [8]. Nginx does something similar with FastCGI page caching, and it\u0026rsquo;s the standard trick behind fast WordPress setups [8]. The catch with full-page caching is obvious: it\u0026rsquo;s brilliant for anonymous, mostly-static pages and terrible for anything personalised. A logged-in dashboard with the user\u0026rsquo;s name in the header can\u0026rsquo;t be blindly full-page cached — you\u0026rsquo;d serve Alice\u0026rsquo;s page to Bob. That\u0026rsquo;s why real setups layer caches: full-page cache for anonymous traffic, and finer-grained object caching underneath for the personalised bits [8].\nApplication and object caching (Redis, Memcached) Now we\u0026rsquo;re inside your server. Application-level caching stores computed results — query results, serialized objects, rendered fragments — in a fast in-memory store so you don\u0026rsquo;t recompute them. The two names you\u0026rsquo;ll hear forever are Redis and Memcached.\nThey\u0026rsquo;re not the same tool:\nRedis Memcached Data types Strings, hashes, lists, sets, sorted sets, streams Simple key/value only Persistence RDB snapshots + AOF log None (pure memory) Threading Mostly single-threaded (per shard) Multi-threaded, scales on multi-core Extras Pub/sub, Lua scripts, transactions, clustering Lean and fast Best when You need structures, persistence, or atomic ops Pure high-throughput get/set Use Redis when you need data structures, persistence, pub/sub or atomic operations beyond simple get/set; reach for Memcached when you just want simple, blisteringly fast get/set for HTML fragments or query results [4]. For most apps Redis is the default because it does more, but Memcached\u0026rsquo;s multi-threaded model genuinely scales better for the narrow get/set case [4].\nThe patterns matter more than the tool How you read and write the cache is where the real decisions live. There are three big patterns [4]:\nCache-aside (lazy loading). The app checks the cache first. On a hit, return it. On a miss, query the database, populate the cache, then return. This is the most common pattern and great for read-heavy workloads where the occasional miss is fine [4]. The downside: the first request for any key is always a miss, and stale data can linger if you don\u0026rsquo;t invalidate on writes. Write-through. Every write updates the database and the cache synchronously. The cache is always warm and consistent, at the cost of slower writes and caching data nobody may ever read [4]. Write-behind (write-back). The app writes only to the cache, and the cache flushes to the database asynchronously [4]. Fastest writes by far — and the scariest, because a crash before the flush loses data. You can mix these. A read-heavy product page might use cache-aside; a shopping cart might use write-through so it\u0026rsquo;s never stale. Pick per data type, not per app.\nDatabase-level caching (the layer you get for free) Even if you add zero caching, your database is already doing it. Most engines keep a buffer pool — recently read pages held in RAM so repeat reads don\u0026rsquo;t hit disk. Some (older MySQL versions) had a full query cache that stored result sets keyed by the exact SQL string, though that\u0026rsquo;s fallen out of favour because invalidation was brutal on write-heavy tables.\nThe lesson: your database\u0026rsquo;s own memory is a cache, and starving it is a classic own-goal. Before you bolt Redis onto everything, make sure your database has enough RAM to keep its working set hot. Sometimes \u0026ldquo;add more buffer pool\u0026rdquo; beats \u0026ldquo;add another moving part.\u0026rdquo;\nMaterialized views and precomputed aggregate tables are a cousin of this idea — you\u0026rsquo;re caching the result of a query as physical rows, refreshed on a schedule. Not glamorous, but for expensive analytics rollups it\u0026rsquo;s often the right call.\nBuild-time and compiled caching Some caching happens before a single user shows up.\nOpcode caching. For interpreted languages, parsing source on every request is pure waste. PHP\u0026rsquo;s OPcache compiles scripts to bytecode once and holds it in shared memory, so later requests skip the parse-and-compile step entirely [9]. For an app with hundreds of files this cuts execution time by roughly 40–70% and slashes CPU use [9]. It\u0026rsquo;s bundled with PHP 5.5+ and is basically mandatory in production. PHP 8\u0026rsquo;s JIT builds on top of it, compiling hot bytecode down to native machine code [9]. Static site generation / ISR. Frameworks like Next.js, Astro and Hugo render pages to static HTML at build time so there\u0026rsquo;s nothing to compute at request time — it\u0026rsquo;s a cache baked into your deploy artifact. Incremental Static Regeneration takes it further, rebuilding individual pages in the background on a schedule so you get static speed with fresher content. Build caches. Your CI, your bundler, Docker layers — they all cache intermediate artifacts so rebuilds only redo what changed. Not user-facing, but it\u0026rsquo;s the same principle: don\u0026rsquo;t redo settled work. Cache invalidation: the part that ruins your week There\u0026rsquo;s an old joke that the two hardest problems in computer science are naming things, cache invalidation, and off-by-one errors. The cache invalidation part is not a joke.\nEvery cache needs an eviction and freshness story. The common eviction policies [6]:\nLRU (Least Recently Used) — evicts whatever hasn\u0026rsquo;t been touched longest. Sensible default, assumes recent = relevant [6]. LFU (Least Frequently Used) — evicts the least-accessed items, keeping popular ones [6]. FIFO — evicts the oldest inserted, ignoring usage. Rarely worth it; LRU almost always beats it [6]. TTL — time-based expiry, the workhorse. But it needs tuning: too short and stable data churns pointlessly; too long and stale data lingers [6]. The cache stampede trap Here\u0026rsquo;s the failure mode that gets people at 2 AM. It\u0026rsquo;s called a cache stampede or thundering herd. A hugely popular cache entry expires, and suddenly every concurrent request misses at the same instant and slams the backend all at once — overwhelming the very database the cache was protecting [6].\nThe sneaky version: you warm a cache in a loop, giving every key an identical TTL. Now every key expires in the same second, manufacturing a stampede on a timer [6]. Ask me how I know.\nFixes, roughly in order of effort:\nAdd jitter. Randomise TTLs so entries don\u0026rsquo;t expire in lockstep. A base of 300s ± a random 60s spreads the load [6]. Locking / single-flight. When a key is being refreshed, let one request rebuild it while the others wait or serve stale [6]. Stale-while-revalidate, again — serve the expired value while one background task refreshes it, so nobody waits on a cold rebuild. And the deeper problem: knowing when your data actually changed so you can invalidate proactively instead of waiting for a TTL. That\u0026rsquo;s genuinely hard, and it\u0026rsquo;s why so many teams accept a bit of staleness rather than chase perfect consistency. Some data — a user\u0026rsquo;s balance, a stock count — can\u0026rsquo;t tolerate that, and for those you write-through and invalidate on every mutation. Everything else, let TTL do the work.\nSo which ones do you actually use? All of them, more or less, at different layers. A well-tuned setup usually looks like this:\nFingerprinted static assets with a one-year, immutable browser cache. A CDN in front, doing 90%+ of the delivery. A reverse proxy or full-page cache for anonymous HTML. Redis (or Memcached) for query results and session data via cache-aside. A database with enough RAM to keep its buffer pool hot. Opcode/build caching so the server never redoes work it did last deploy. The browser cache, CDN and server-side caches are each strong alone, but the real gains come from stacking them so a request has to fall through many layers before it does any real work [1]. Start at the top — the browser — because that\u0026rsquo;s the cheapest hit, and only push work down to the database when you truly have no choice.\nGet the layering right and most of your traffic never wakes your origin at all. Get the invalidation wrong and you\u0026rsquo;ll spend a week explaining why the price on the page is from last Tuesday. Both are part of the deal.\nSources Caching Strategies: Browser Cache, CDN, and Server-Side Caching — GrabPerf Cache-Control header — MDN Web Docs Caching — Progressive web apps | MDN System Design: Distributed Caching — Redis vs Memcached, Cache-Aside, Write-Through CDN Cache-Hit Ratio Optimization Strategies — BlazingCDN How to Handle Cache Stampede (Thundering Herd) in Redis — OneUptime Cache Busting Strategies With CDNs And Browser Caching — DCHost The fundamentals of web proxy caching with Varnish PHP Performance: Everything You Need to Know About OpCode Caches — Engine Yard ","permalink":"https://cloudmato.com/posts/ways-to-cache-data-for-websites-web-apps/","title":"Every Way to Cache Data for Websites and Web Apps"},{"content":"Everyone is building \u0026ldquo;REST APIs\u0026rdquo; nowadays. You have got endpoints, you return JSON, you use GET and POST and DELETE, you send back 404 when something is missing. Congratulations, you have a REST API. Right?\nWell, according to the person who literally coined the term REST, probably not. And the piece you are missing is a scary-looking acronym called HATEOAS — the one part of REST that almost nobody implements and most people can\u0026rsquo;t even spell. I have been writing APIs for more than 8 years and honestly, for the longest time I treated HATEOAS as academic trivia. So let me untangle it: what it is, why the industry collectively ignores it, and the more interesting question — when you actually earn the right to call your thing a REST API.\nWhat HATEOAS actually stands for HATEOAS is Hypermedia As The Engine Of Application State. Ugly acronym, simple-ish idea.\nThe core claim is this: a client should be able to interact with your API using nothing but the links your server hands back in its responses. Each response doesn\u0026rsquo;t just carry data — it carries the data plus links telling the client what it can do next [1]. The client shows up knowing one entry URL and a media type, and from there the server drives the whole conversation by offering choices.\nThink about how you use a website. You don\u0026rsquo;t memorize URLs. You land on a homepage, you see links and buttons, you click them, the next page shows you the next set of links. You never had to read documentation that said \u0026ldquo;to view your cart, issue a GET to /cart/items?userId=...\u0026rdquo;. The page told you where to go. That\u0026rsquo;s hypermedia as the engine of application state — the current page (state) is driven by the links (hypermedia) it contains [1].\nHATEOAS says your JSON API should work the same way. A machine client, not a human, but same principle: the server tells the client what\u0026rsquo;s possible next, the client doesn\u0026rsquo;t hardcode it.\nHere\u0026rsquo;s what a plain response looks like versus a HATEOAS one.\nPlain, the way 95% of us write it:\n{ \u0026#34;id\u0026#34;: 12, \u0026#34;status\u0026#34;: \u0026#34;PENDING\u0026#34;, \u0026#34;total\u0026#34;: 4999 } The same thing with hypermedia controls:\n{ \u0026#34;id\u0026#34;: 12, \u0026#34;status\u0026#34;: \u0026#34;PENDING\u0026#34;, \u0026#34;total\u0026#34;: 4999, \u0026#34;_links\u0026#34;: { \u0026#34;self\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/orders/12\u0026#34; }, \u0026#34;pay\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/orders/12/payment\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;POST\u0026#34; }, \u0026#34;cancel\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/orders/12/cancel\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;POST\u0026#34; } } } See the difference? The second one is telling the client \u0026ldquo;hey, this order is pending, so you can either pay for it or cancel it.\u0026rdquo; If the order were already shipped, the server just wouldn\u0026rsquo;t include those links — no cancel link means you can\u0026rsquo;t cancel, and the client doesn\u0026rsquo;t need business logic to figure that out. That\u0026rsquo;s the pitch. It\u0026rsquo;s genuinely elegant.\nWhere this idea comes from (and why it matters) This isn\u0026rsquo;t some blog-invented best practice. It comes straight from Roy Fielding\u0026rsquo;s 2000 doctoral dissertation, the document that defined REST in the first place. REST has six constraints — client-server, stateless, cacheable, layered system, code-on-demand (optional), and the big one, uniform interface [5].\nThe uniform interface itself breaks into four sub-constraints: identification of resources (URIs), manipulation through representations, self-descriptive messages, and — you guessed it — hypermedia as the engine of application state [5]. So HATEOAS isn\u0026rsquo;t a nice-to-have bolted onto REST. In Fielding\u0026rsquo;s model it\u0026rsquo;s one of the load-bearing walls.\nAnd Fielding got tired enough of people ignoring it that in 2008 he wrote a famously grumpy blog post titled \u0026ldquo;REST APIs must be hypertext-driven\u0026rdquo;. The money quote:\n\u0026ldquo;If the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API.\u0026rdquo; [2]\nHe goes further:\n\u0026ldquo;A REST API should be entered with no prior knowledge beyond the initial URI and set of standardized media types\u0026hellip; From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations.\u0026rdquo; [2]\nRead that carefully. By Fielding\u0026rsquo;s own definition, the thing you built — the one where the client has a hardcoded list of endpoints copied from your Swagger docs — is not a REST API. It might be a perfectly good HTTP API. It\u0026rsquo;s just not REST in the strict sense. This is the misconception at the heart of the whole topic: \u0026ldquo;REST\u0026rdquo; as the industry uses it is not the REST Fielding defined. The htmx folks have a whole essay on how REST came to mean the opposite of REST, and they are not wrong.\nThe Richardson Maturity Model — a ladder for how RESTful you are Before we bash HATEOAS, it helps to see where it sits. Leonard Richardson proposed a maturity model in 2008, later popularized by Martin Fowler, that grades your API on three axes: URIs, HTTP verbs, and hypermedia controls [3][4]. It gives you four levels.\nLevel Name What you use Reality 0 The Swamp of POX One URI, one verb (usually POST) SOAP, XML-RPC. Tunneling everything through a single endpoint 1 Resources Many URIs, still one verb Each thing has its own address, but everything is POST 2 HTTP Verbs URIs + proper GET/POST/PUT/DELETE + status codes This is where 90% of \u0026ldquo;REST APIs\u0026rdquo; actually live 3 HATEOAS Everything above + hypermedia links The \u0026ldquo;pinnacle.\u0026rdquo; Rarely reached Fowler is careful to say these levels are a nice way to think about the ingredients of REST, not a strict scoring you must max out [4]. But the picture is clear: what everybody calls \u0026ldquo;REST\u0026rdquo; is really Level 2 — sensible URLs, correct verbs, meaningful status codes [3]. Level 3, the hypermedia layer, is the one that gets skipped.\nSo why does almost nobody implement it? This is the part I actually find interesting, because HATEOAS looks great on a slide and then quietly dies the moment you try to ship it. There are real reasons, not just laziness. Let me go through the honest ones.\n1. The clients that would benefit don\u0026rsquo;t exist The whole promise of HATEOAS rests on a generic client — some smart consumer that shows up, reads links, and navigates your API without being pre-programmed for it. That client is basically mythical outside of the web browser [6].\nIn the real world your API is consumed by a React frontend, an iOS app, and three backend services — all of which were written by the same team, in the same sprint, tightly coupled to your specific API [6]. They are not generic. They already know your endpoints because your teammate wrote them last Tuesday. Handing a hardcoded React app a _links.pay field doesn\u0026rsquo;t make it magically flexible; the developer still hardcodes order._links.pay.href instead of /orders/${id}/payment. You added ceremony, not freedom.\n2. The promised benefit — \u0026ldquo;evolve your URLs freely\u0026rdquo; — mostly doesn\u0026rsquo;t materialize The headline sales pitch is: with HATEOAS you can move your endpoints around and clients just follow the new links, no coordination needed. Sounds beautiful.\nIn practice it falls apart. As Ben Morris points out in his pragmatic-REST piece, if you change your endpoints, you still don\u0026rsquo;t get obedient clients following links to the new location — because real clients hardcoded the URLs anyway, or depend on the link relation names (rel: \u0026quot;pay\u0026quot;) which now become the contract you can\u0026rsquo;t change [7]. You just moved the coupling from URLs to relation names. Congratulations. The decoupling was theoretical.\n3. No standard format, and everyone does it differently Say you buy the vision and want to add hypermedia. Cool — in what format? Here\u0026rsquo;s the mess:\nHAL — simple, _links and _embedded, the most popular, created by Mike Kelly in 2011 [8]. Siren — adds explicit actions describing methods and expected fields, good for workflows [8]. JSON:API — its own whole spec with links and relationships, decent tooling [8]. JSON-LD / Hydra, Collection+JSON, and a few others. There is no de facto winner. Every one of these formats is somebody\u0026rsquo;s opinion, and picking one means betting on tooling and hoping the next developer knows it [8]. Andreas Reiser argues in \u0026ldquo;Why HATEOAS is useless\u0026rdquo; that even with a format agreed, you still can\u0026rsquo;t communicate the meaning of the data through links, so a truly generic client remains impossible. That\u0026rsquo;s the deep problem — links tell you where, not what it means.\n4. It bloats responses and adds complexity Every response now carries a pile of link boilerplate. Your GET /orders that returns 50 orders now returns 50 orders each wrapped in _links objects with 3-5 links apiece. The payload balloons, and someone has to write and test the logic that decides which links to include based on state. It\u0026rsquo;s real work for a benefit your clients aren\u0026rsquo;t set up to use [6].\n5. The tooling and docs won the war instead Here\u0026rsquo;s the pragmatic truth. HATEOAS was supposed to make APIs self-describing so you wouldn\u0026rsquo;t need out-of-band documentation. But then OpenAPI/Swagger showed up and gave us machine-readable docs, code generators, interactive explorers, and mock servers. GraphQL went further — it\u0026rsquo;s natively discoverable through its schema, so a client can introspect the whole API without any hypermedia at all [10]. Discoverability got solved by other means, and those means had great tooling. HATEOAS never got that ecosystem [6].\nBut wait — some big players DO use it I don\u0026rsquo;t want to be unfair. HATEOAS isn\u0026rsquo;t vaporware. Several serious APIs use it, at least partially:\nPayPal returns a links array on payment responses, each with href, rel, and method, so you construct the payment flow by following self, refund, parent_payment and friends [9]. This one actually makes sense — payment flows are stateful multi-step processes where \u0026ldquo;what can I do next\u0026rdquo; genuinely changes. GitHub\u0026rsquo;s API is littered with *_url fields — fetch a repo and you get issues_url, pulls_url, commits_url pointing you onward [11]. It\u0026rsquo;s not a strict HAL format, but the spirit is there. AWS AppStream, Visa, and Stormpath have used hypermedia too, and Spring even ships Spring HATEOAS to make it easier in Java land. Notice the pattern? HATEOAS earns its keep in workflow-heavy, stateful domains — payments, provisioning, multi-step approvals — where \u0026ldquo;the available actions depend on current state\u0026rdquo; is a genuinely hard problem worth encoding in the response. For a plain CRUD-over-a-database API, it\u0026rsquo;s mostly ceremony. That\u0026rsquo;s the nuance people miss when they either dismiss it entirely or evangelize it blindly.\nSo when can I actually call my API a \u0026ldquo;REST API\u0026rdquo;? This is the question you actually came for, so let me give you a straight answer and then the honest answer.\nThe strict answer (Fielding\u0026rsquo;s): you can call it a REST API only when it hits Level 3 — resources with URIs, proper use of HTTP verbs and status codes, self-descriptive messages, and hypermedia driving state transitions [2][5]. No hypermedia, no REST. Full stop. By this bar, GitHub is close, PayPal\u0026rsquo;s payment flow qualifies, and your typical microservice does not.\nThe honest, industry answer: the word \u0026ldquo;REST\u0026rdquo; has drifted. When a job posting or a teammate says \u0026ldquo;REST API,\u0026rdquo; they mean an HTTP API that:\nUses resource-based URLs — /orders/12, not /getOrder?id=12. Uses HTTP verbs correctly — GET reads, POST creates, PUT/PATCH updates, DELETE removes. Uses HTTP status codes meaningfully — 200, 201, 404, 400, 500 mean what they should. Is stateless — each request carries everything the server needs; no server-side session leaning. Returns a consistent representation — usually JSON, with sane structure. That\u0026rsquo;s Richardson Level 2, and that\u0026rsquo;s what the entire industry has agreed to call \u0026ldquo;REST\u0026rdquo; [3][4]. Fielding would grumble, and he\u0026rsquo;d be technically correct, but this ship sailed a long time ago. Stefan Tilkov\u0026rsquo;s classic \u0026ldquo;REST APIs must be hypertext-driven\u0026rdquo; commentary and a decade of practice landed on a comfortable compromise: most people say \u0026ldquo;RESTful\u0026rdquo; for Level 2 and reserve \u0026ldquo;hypermedia API\u0026rdquo; or \u0026ldquo;HATEOAS-compliant\u0026rdquo; for the real Level 3 thing.\nMy personal take after years of shipping these: don\u0026rsquo;t stress about the purity badge. Aim for a clean Level 2 and you have built something 99% of consumers will happily call REST. Add hypermedia selectively where the domain is genuinely stateful and a link like refund or cancel carries real business meaning. Don\u0026rsquo;t sprinkle _links everywhere just to appease a dissertation from 2000. That\u0026rsquo;s cargo-cult engineering.\nA quick reality check on the whole debate Here\u0026rsquo;s the thing that took me a while to accept. The reason HATEOAS \u0026ldquo;failed\u0026rdquo; in practice isn\u0026rsquo;t that it\u0026rsquo;s a bad idea — it\u0026rsquo;s that it solved a problem most people don\u0026rsquo;t have. It was designed for a web-scale world of long-lived, independently-evolving, generic clients following links the way browsers follow anchor tags. Most of us are building a frontend and a backend that ship together, version together, and die together. For that world, tight coupling is fine and hypermedia is overhead.\nAnd where the original vision was right — the browser and the HTML web — HATEOAS is wildly successful. You just don\u0026rsquo;t call it HATEOAS. You call it \u0026ldquo;the web.\u0026rdquo; The htmx project makes exactly this argument: HTML is the working hypermedia format, and JSON APIs abandoned hypermedia the moment they became the plumbing between machine components rather than something a human navigates.\nSo next time someone asks whether your API is \u0026ldquo;properly RESTful,\u0026rdquo; you have got two honest answers ready. Technically? Probably not, and here\u0026rsquo;s the exact reason why. Practically? It\u0026rsquo;s Level 2, it\u0026rsquo;s clean, it\u0026rsquo;s what everyone means by REST, and adding HATEOAS wouldn\u0026rsquo;t buy your React app a single thing. Both answers are true at once, and knowing which one to give in the room is the actual skill.\nSources How to Build HATEOAS Driven REST APIs — restfulapi.net REST APIs must be hypertext-driven — Roy T. Fielding Richardson Maturity Model — restfulapi.net Richardson Maturity Model — Martin Fowler Fielding Dissertation, Chapter 5: Representational State Transfer Do People Really Use HATEOAS in REST APIs? — Pradeesh Kumar Pragmatic REST: APIs without hypermedia and HATEOAS — Ben Morris A Deep Dive into Alternative Data Formats for APIs: HAL, Siren, and JSON-LD — Zuplo API responses (HATEOAS links) — PayPal Developer Docs GraphQL vs REST — Postman Blog Using HATEOAS with REST APIs — 3ap Engineering Blog REST APIs Must be Hypertext-driven — Stefan Tilkov, INNOQ HATEOAS essay — htmx.org How Did REST Come To Mean The Opposite of REST? — htmx.org Why HATEOAS is useless and what that means for REST — Andreas Reiser ","permalink":"https://cloudmato.com/posts/hateoas-rest-api-why-nobody-implements-it/","title":"HATEOAS: What It Is and Why Almost No One Uses It"},{"content":"Redis is one of those rare pieces of software where the source code is short enough to actually read and deep enough to teach you something new every time. I\u0026rsquo;ve spent a good amount of time going through the actual Redis source — and what strikes me every time is how deliberately every design choice was made. Nothing is accidental.\nSo let\u0026rsquo;s actually design one from the ground up — not a toy, but an architecture that mirrors what Redis actually does. Data structures, event loop, protocol, expiry, persistence. All of it.\nThe Big Picture First Before a single line of C, it helps to understand what Redis is. Redis is a self-described \u0026ldquo;data structure store\u0026rdquo; — not a cache, not a database (well, it can be those too), but at its core, a server that lets you operate on named data structures over a network. It keeps everything in RAM and executes one command at a time on a single thread.\nThat last part always surprises people. A single thread. And yet Redis handles hundreds of thousands of operations per second. The trick is the event loop.\nThe Event Loop: One Thread, Many Connections Redis wraps the OS-level I/O multiplexing APIs in a thin abstraction layer called ae (Async Events). At compile time it picks the best backend available:\nLinux → epoll macOS → kqueue Others → select (fallback) The actual source is in src/ae.c. The idea is simple: instead of spawning a thread per client, Redis registers all client sockets with epoll_wait() (or equivalent) and blocks until something happens. The kernel wakes Redis up, Redis reads the command, executes it, writes back a response — then polls again. No context switches, no locks, no condition variables.\nThe core loop in C looks roughly like this:\nvoid aeMain(aeEventLoop *eventLoop) { eventLoop-\u0026gt;stop = 0; while (!eventLoop-\u0026gt;stop) { aeProcessEvents(eventLoop, AE_ALL_EVENTS | AE_CALL_BEFORE_SLEEP | AE_CALL_AFTER_SLEEP); } } Every tick of that loop does three things:\nPoll all registered file descriptors via epoll/kqueue Fire I/O callbacks (read from client, write response back) Fire time callbacks (key expiry cycles, background saves) The reason this works at scale is that Redis commands execute in microseconds. The CPU never sits idle waiting for I/O — it\u0026rsquo;s always either doing useful work or parked in the kernel waiting for the next event.\nRedis 6.0 introduced multi-threaded I/O for reading and writing to sockets, but actual command execution still happens on a single thread. The core invariant is preserved [6].\nRESP: The Protocol That\u0026rsquo;s Almost Embarrassingly Simple Every Redis client speaks RESP — the Redis Serialization Protocol. It\u0026rsquo;s text-based, line-oriented, and you can read a raw packet with netcat and understand it immediately. Here\u0026rsquo;s what SET foo bar looks like on the wire:\n*3\\r\\n $3\\r\\n SET\\r\\n $3\\r\\n foo\\r\\n $3\\r\\n bar\\r\\n That\u0026rsquo;s a RESP array (*3) containing three bulk strings ($3). The single-character type prefix tells you what follows:\nPrefix Type Example + Simple string +OK\\r\\n - Error -ERR unknown\\r\\n : Integer :42\\r\\n $ Bulk string $3\\r\\nfoo\\r\\n * Array *2\\r\\n... Parsing this in C is maybe 60 lines. The simplicity is intentional — RESP makes it trivial to debug with raw netcat, easy to implement clients in any language, and parseable with zero external dependencies.\nSDS: Because C Strings Are a Trap Here\u0026rsquo;s the first real design decision: Redis doesn\u0026rsquo;t use C\u0026rsquo;s native null-terminated char* strings. It uses SDS — Simple Dynamic Strings, defined in src/sds.h.\nWhy? Because C strings are terrible for a database:\nstrlen() is O(n) — unacceptable when called millions of times per second They can\u0026rsquo;t store binary data (a null byte terminates the string) Appending requires a manual realloc on every call SDS solves all three. Here\u0026rsquo;s the core structure (simplified to the most common variant):\nstruct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used bytes in buf */ uint64_t alloc; /* total allocated, excluding header */ unsigned char flags; /* which sdshdr type? */ char buf[]; /* actual string bytes */ }; The trick: the buf pointer is what gets returned to calling code, making it compatible with any standard C string function. But because the header is stored just before buf, you subtract sizeof(struct sdshdr) to get the length in O(1) [3].\nThe memory pre-allocation strategy prevents realloc churn:\nNew length \u0026lt; 1MB → allocate 2× new length New length ≥ 1MB → allocate new length + 1MB (linear growth) And when you shrink a string, SDS doesn\u0026rsquo;t immediately free the memory — the alloc field stays large, len drops, and a future append reuses the slack. Classic lazy shrinking.\nThere are actually five SDS header types (sdshdr5 through sdshdr64) to minimise header overhead. A 10-character string doesn\u0026rsquo;t need a 64-bit length field — it gets sdshdr8 with a one-byte length counter.\nThe Hash Table (dict.c): Two Tables and the Rehashing Trick Redis\u0026rsquo;s hash table is in src/dict.c and it\u0026rsquo;s worth reading in full. The outer struct:\ntypedef struct dict { dictType *type; dictEntry **ht_table[2]; /* TWO hash tables */ long long ht_used[2]; long rehashidx; /* -1 = no rehash in progress */ int16_t pauserehash; } dict; Notice the two hash tables. That\u0026rsquo;s the key to Redis\u0026rsquo;s approach to resizing.\nThe problem with naive rehashing: when a hash table grows past its load factor, you allocate a bigger table and migrate all keys. Migrating millions of keys is O(N) — that blocks the event loop for a noticeable pause. Unacceptable in a latency-sensitive system.\nThe solution: incremental rehashing [2]. When resizing kicks in:\nA second, larger table (ht_table[1]) is allocated rehashidx is set to 0 (first bucket to migrate) Every subsequent dict operation migrates a small batch of buckets from ht_table[0] to ht_table[1], advancing rehashidx Reads check both tables during the transition New keys go into ht_table[1] only When ht_table[0] is empty, swap and reset The cost gets distributed across normal operations. Each command pays a tiny incremental tax instead of one enormous bill.\nCollision resolution is chaining — each bucket holds a linked list of entries:\ntypedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; /* next in chain */ void *metadata[]; } dictEntry; The union for the value is a neat optimisation — small integers and doubles are stored directly in the entry struct without an extra heap allocation.\nThe Object System: redisObject Wraps Everything Every key and value in Redis is wrapped in a redisObject (typedefed robj). From src/server.h:\ntypedef struct redisObject { unsigned type:4; /* OBJ_STRING, OBJ_LIST, OBJ_SET, ... */ unsigned encoding:4; /* OBJ_ENCODING_HT, OBJ_ENCODING_SKIPLIST, ... */ unsigned lru:LRU_BITS; /* LRU clock or LFU counter */ int refcount; void *ptr; /* actual data */ } robj; Four bits for type, four bits for encoding — they fit in a single byte together. The ptr points to the actual data structure, but which structure depends on encoding, not type. This is how Redis achieves its dual-encoding magic:\nType Small (listpack / intset) Large encoding String INT or EMBSTR (≤ 44 bytes) Raw SDS List LISTPACK (≤ 128 elements) QUICKLIST Hash LISTPACK (≤ 128 fields) HT (dict) Set LISTPACK or INTSET HT (dict) Sorted Set LISTPACK (≤ 128 members) SKIPLIST + HT A Redis hash with 50 small fields uses a flat listpack — no hash table at all, just a contiguous array. The moment you push past the threshold, Redis transparently converts it to a dict. The client never sees this transition [7].\nThe refcount enables shared objects. Redis preallocates integers 0–9999 as permanent shared robj instances. If a value is the integer 42, multiple keys can point to the same object without duplicating memory.\nLists: The Quicklist Redis lists are a quicklist — a doubly-linked list where each node is a small listpack (compact array) of multiple elements.\ntypedef struct quicklist { quicklistNode *head; quicklistNode *tail; unsigned long count; /* total entries across all nodes */ unsigned long len; /* number of quicklistNode */ } quicklist; typedef struct quicklistNode { struct quicklistNode *prev; struct quicklistNode *next; unsigned char *entry; /* listpack bytes */ size_t sz; unsigned int count : 16; unsigned int encoding : 2; /* RAW=1, LZF=2 */ } quicklistNode; Pure listpack: memory-efficient and cache-friendly but slow to grow — appending to a large listpack shifts data [9]. Pure linked list: O(1) head/tail ops but wastes memory on pointers. Quicklist gives you both.\nMiddle nodes can even be LZF-compressed, which is great for queue patterns where only head and tail are ever touched.\nSorted Sets: Skiplist + Hash Table — Two Data Structures at Once This is honestly my favourite design in all of Redis. Sorted sets need to support two wildly different access patterns simultaneously:\n\u0026ldquo;What is the score of member X?\u0026rdquo; → O(1) lookup by name \u0026ldquo;Get all members with scores between 10 and 50, in order\u0026rdquo; → O(log N) range scan No single data structure handles both optimally. So Redis uses two — at the same time [5]:\nA dict (hash table) for O(1) member → score lookups A skiplist for O(log N) ordered range operations The SDS string for each member is shared between both structures as a pointer — no duplication. The skiplist is defined in src/t_zset.c:\ntypedef struct zskiplistNode { sds ele; /* the member string */ double score; struct zskiplistNode *backward; struct zskiplistLevel { struct zskiplistNode *forward; unsigned long span; /* nodes skipped to reach forward */ } level[]; /* variable-length */ } zskiplistNode; typedef struct zskiplist { struct zskiplistNode *header, *tail; unsigned long length; int level; } zskiplist; The span field is a Redis-specific addition to the classic skip list algorithm — it counts how many nodes you skip when following a forward pointer at each level. This lets Redis calculate the rank of any element in O(log N) without a separate rank tree [4].\nLevel assignment is probabilistic: every new node gets level 1. With probability 0.25 it also gets level 2, with probability 0.25² it gets level 3, up to a cap of 64. This gives O(log N) expected search time without tree rotations or rebalancing logic.\nKey Expiry: Two Strategies, One Memory Leak Risk When you run SET foo bar EX 60, Redis doesn\u0026rsquo;t start a timer. It records the absolute UNIX expiry timestamp (in milliseconds) in a separate dict called db-\u0026gt;expires. The key in db-\u0026gt;dict, the expiry in db-\u0026gt;expires. Two parallel dicts.\nTwo strategies run together to clean up expired keys:\nLazy Expiry (Passive) Every time a client reads a key, Redis checks db-\u0026gt;expires first. If expired — delete it, return nil. Simple, cheap, correct. But here\u0026rsquo;s the hole: if a key is never accessed after it expires, it stays in memory indefinitely. That\u0026rsquo;s a real memory leak for set-and-forget patterns.\nActive Expiry (Background Sampling) Redis runs an expiry cycle hz times per second (default: 10, meaning every 100ms). Each cycle [8]:\n/* Simplified from expire.c */ void activeExpireCycle(int type) { do { num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP; /* 20 */ expired = 0; while (num--) { de = dictGetRandomKey(db-\u0026gt;expires); if (!de) break; if (activeExpireCycleTryExpire(db, de, now)) expired++; } /* repeat if \u0026gt; 25% of sample was expired */ } while (expired \u0026gt; ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 4); } Sample 20 random keys. Delete the expired ones. If more than 25% of the sample was stale, repeat — because a high stale ratio implies many more stale keys still lurk. This terminates quickly in healthy databases but adapts under expiry storms.\nOne important replication behaviour: replicas do not independently delete expired keys. They wait for DEL commands propagated from the primary. Since Redis 3.2, a replica refuses to serve an expired key to a client even before the DEL arrives — but the memory isn\u0026rsquo;t freed until the primary says so.\nPersistence: Snapshots and the Write Log Redis lives in RAM. Surviving a restart requires one (or both) of two mechanisms:\nRDB — Point-in-Time Snapshots BGSAVE forks the process. The child writes a compact binary .rdb file of the entire dataset. The parent keeps serving requests. Copy-on-write semantics give the child a frozen view of memory — pages the parent modifies get duplicated by the kernel, not by Redis code.\nRestart is fast: deserialise the RDB. Downside: anything written between the last snapshot and the crash is lost [1].\n/* From rdb.c — the high-level flow */ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { if ((childpid = fork()) == 0) { /* Child process */ rdbSave(filename, rsi); _exit(exitcode); } /* Parent continues serving clients */ } AOF — Append-Only File Every write command is appended to a log file after it executes. On restart, replay the log from the beginning. Durability is configurable:\nappendfsync Behaviour Durability always fsync after every command Highest (slowest) everysec fsync once per second ≤1s loss (default) no OS decides Fastest (least safe) AOF files grow forever, so Redis rewrites them periodically — fork a child, write the minimal command set to represent current state, atomically replace the old file. Since Redis 7.0, the AOF is split into base + incremental files (multi-part AOF) for more efficient rewriting [10].\nHybrid mode (Redis 4.0+): the AOF file begins with an RDB snapshot, followed by incremental AOF commands. Fast restarts from the embedded RDB snapshot, minimal data loss from the AOF tail.\nPutting It All Together: The GET Command Walk-Through When you send GET foo to Redis, here\u0026rsquo;s the full chain from socket bytes to response:\nepoll_wait() returns — the client fd has data readQueryFromClient() reads bytes into client-\u0026gt;querybuf (an SDS) processInputBuffer() parses RESP → fills client-\u0026gt;argv[] processCommand() looks up \u0026quot;GET\u0026quot; in server.commands (a dict) getCommand() calls lookupKeyRead(db, key) → probes db-\u0026gt;dict Checks db-\u0026gt;expires for TTL — delete and return nil if expired Dereferences robj-\u0026gt;ptr depending on robj-\u0026gt;encoding to get the value Serialises result as a RESP bulk string into client-\u0026gt;buf writeToClient() flushes the buffer back over the socket The entire round-trip for a cache hit on a simple string — microseconds. That\u0026rsquo;s before any threading or cluster tricks.\nWhere to Start If You\u0026rsquo;re Building One I\u0026rsquo;d sequence the implementation like this:\nRESP parser + TCP server — accept raw connections, parse commands, respond +OK. Use epoll from day one, not threads per connection. SDS — implement the header trick and pre-allocation before anything touches strings. dict with incremental rehashing — hardest part. Budget time for it. redisObject wrapper — type + encoding fields so you can swap internals transparently later. SET / GET / DEL / EXPIRE — the core four on string keys. Lazy expiry — add db-\u0026gt;expires and check it on every read. Lists and sorted sets — quicklist and skiplist come after the core is solid. RDB persistence — fork() + binary serialisation. Use the real system call, not a thread. Active expiry cycle — background sampling loop via ae time events. The Build Your Own Redis with C/C++ book is the most structured guide I\u0026rsquo;ve found for actually implementing this step by step. The real Redis source — specifically the annotated src/dict.c, src/sds.c, and src/t_zset.c — is the ground truth.\nThe real learning isn\u0026rsquo;t in getting SET/GET to respond correctly. It\u0026rsquo;s in making the hash table not block under a write storm during rehashing, and making expiry not leak memory for keys that are never accessed. Those edge cases are exactly where every design decision Redis made starts making sense.\nEnd\nSources Redis Persistence | Docs Analyzing Redis Source Code: Hash Tables, Chained Hashing, and Incremental Rehashing Redis SDS — String Internals | Docs Redis Sorted Sets — Under the Hood redis/src/t_zset.c at unstable · redis/redis The Engineering Wisdom Behind Redis\u0026rsquo;s Single-Threaded Design Redis Deep Dive Part 2 — The Building Blocks How Redis Expires Keys — A Deep Dive into How TTL Works Internally Redis Quicklist — From a More Civilized Age Redis Persistence Explained: AOF \u0026amp; RDB GitHub — redis/redis (official source) Build Your Own Redis with C/C++ GitHub — antirez/sds: Simple Dynamic Strings library for C redis/src/quicklist.h at unstable · redis/redis RESP3 Protocol Specification ","permalink":"https://cloudmato.com/posts/build-redis-from-scratch-data-structures-system-design/","title":"Build Redis From Scratch: Data Structures \u0026 System Design"},{"content":"The browser is a hostile environment. That one sentence explains more about why SPA authentication goes wrong than any whitepaper ever has. You\u0026rsquo;re shipping your entire application — including your auth logic — to a machine you don\u0026rsquo;t control, in a runtime where malicious scripts, rogue browser extensions, and network-level attackers are all operating in the same sandpit. Get the design wrong and you\u0026rsquo;re not just leaking a JWT. You\u0026rsquo;re handing over session persistence, user identity, and trust — all at once.\nHere\u0026rsquo;s how to build an auth system for a single-page application that actually holds up under real-world pressure: PKCE-based SSO, proper MFA, non-blocking token refresh, and defense against XSS and CSRF baked in from the start.\nThe Core Problem: A SPA Cannot Keep a Secret Every auth decision in a SPA flows from one uncomfortable truth: a SPA is a public OAuth client. There\u0026rsquo;s no server-side runtime to hide your client secret. Your JavaScript ships to the browser, and anyone who opens DevTools can read it. This rules out the Client Credentials Flow and — correctly, since it was deprecated — the Implicit Flow entirely[1].\nThe OAuth 2.0 Security Best Current Practice (RFC 9700, published January 2025) puts it plainly: Authorization Code Flow with PKCE is now mandatory for browser-based applications[1]. Not recommended. Mandatory.\nPKCE — Proof Key for Code Exchange — closes the gap where an attacker intercepts the authorization code before your SPA can exchange it for tokens. The SPA generates a cryptographically random code_verifier, hashes it into a code_challenge, sends the challenge to the authorization server with the initial request, and proves ownership of the original verifier at token exchange time[3]. An intercepted authorization code is useless to an attacker who doesn\u0026rsquo;t have the verifier. That\u0026rsquo;s the whole point.\nWhere to Actually Store Tokens This is the question that produces more wrong answers than any other. Let me just say it directly.\nNever store tokens in localStorage or sessionStorage. Both are readable by any JavaScript running on the page — including a script injected by an XSS attack. If an attacker gets arbitrary JS execution in your app, localStorage.getItem('access_token') gives them everything in one line[5].\nThe setup that actually works:\nToken Storage Location Why It Works Access Token (~15 min TTL) JS memory (module variable or React context) Invisible to XSS scripts; cleared on reload Refresh Token (hours–days TTL) HttpOnly; Secure; SameSite=Strict cookie Completely inaccessible to JS; sent only over HTTPS Yes, storing the access token in memory means it\u0026rsquo;s gone on page reload. That\u0026rsquo;s fine — the refresh token cookie survives the reload, and you silently exchange it for a new access token when the app initialises. The Auth0 refresh token rotation guide covers this pattern in depth[2].\nThe refresh token cookie should also be scoped to only your token refresh endpoint (path=/auth/token or similar), not to path=/. Scoping limits where the browser will attach that cookie, reducing its exposure window considerably.\nOne more thing: refresh token rotation is not optional. After every successful token refresh, the server issues a new refresh token and invalidates the old one[2]. This enables reuse detection — if a stolen refresh token gets used after the legitimate user has already rotated past it, the server detects the anomaly and can invalidate the entire token family, forcing re-authentication for everyone holding tokens in that lineage[12].\nSSO with OpenID Connect: What\u0026rsquo;s Actually Happening OpenID Connect (OIDC) sits on top of OAuth2 and handles the identity layer — the \u0026ldquo;who are you\u0026rdquo; part, not just \u0026ldquo;what are you allowed to do.\u0026rdquo; When you integrate with an IdP (Okta, Auth0, Keycloak, whatever), OIDC is the protocol running the SSO.\nHere\u0026rsquo;s the elegant bit: after the first successful login, the IdP plants its own session cookie in the user\u0026rsquo;s browser. The next time your SPA sends the user to the IdP\u0026rsquo;s authorization endpoint, the IdP sees that session cookie, skips the login form entirely, and immediately issues a new authorization code. From the user\u0026rsquo;s perspective it\u0026rsquo;s seamless[14].\nThe nonce parameter is your protection against ID token replay attacks here. Your SPA generates a cryptographically random nonce, includes it in the authorization request, and then verifies that the same nonce appears in the ID token the IdP returns. An attacker who intercepts a valid ID token from a previous login and tries to replay it is dead in the water — the nonce won\u0026rsquo;t match[14]. Always validate the nonce. Always. It\u0026rsquo;s one of those skips that seems harmless until it isn\u0026rsquo;t.\nLogout is where teams cut corners, and it bites them. A proper SSO logout has three steps:\nCall the IdP\u0026rsquo;s token revocation endpoint (/oauth2/revoke) to kill the refresh token server-side Clear in-memory tokens and local session state in the SPA Redirect the user to the IdP\u0026rsquo;s end_session_endpoint to kill the SSO session cookie too[11] Skip step 3 and your user is \u0026ldquo;logged out\u0026rdquo; of your app but still has a live IdP session. Anyone who opens a browser on that machine — a colleague, a family member, an attacker with physical access — walks straight back in[11].\nMFA in 2025: TOTP Is the Floor, Not the Ceiling TOTP (those six-digit codes from Google Authenticator or Authy) has been the MFA workhorse for years, and it\u0026rsquo;s still a perfectly reasonable option as a fallback. But here\u0026rsquo;s the thing most security discussions don\u0026rsquo;t say clearly enough: TOTP is phishable. A real-time phishing proxy can intercept the code and replay it before the 30-second window closes. It\u0026rsquo;s better than nothing. It\u0026rsquo;s not the end state.\nNIST SP 800-63-4 now distinguishes authentication assurance levels. For AAL2 — the level most production apps need — you should offer at least one phishing-resistant factor[15]. That means:\nFIDO2/WebAuthn hardware keys (YubiKey, etc.): the credential is cryptographically bound to the specific origin domain. A fake phishing site with a different domain gets a credential that\u0026rsquo;s useless for yours. Passkeys: synced through iCloud Keychain, Google Password Manager, or similar. Passkeys are now officially AAL2 under NIST SP 800-63-4[15]. Every major browser supports them. 2025 is the year they genuinely went mainstream[15]. Implementing MFA in a SPA doesn\u0026rsquo;t require building your own challenge flow. OIDC handles it transparently: you request a higher acr_values in the authorization request, the IdP challenges the user for the appropriate second factor, and you get back an ID token that attests to the authentication strength. Verify the amr (Authentication Methods References) claim to confirm MFA actually ran — don\u0026rsquo;t just trust that it did[3].\nToken Refresh Without Blocking the UI Honestly, this is where most implementations have subtle but nasty bugs.\nThe naive approach: when an API call returns 401, refresh the token and retry. The problem? In a real SPA you have dozens of concurrent API requests. If three of them hit 401 at the same moment, you end up with three concurrent refresh calls — a race condition that can trigger rotation failures and boot the user out completely.\nThe correct approach uses a single in-flight refresh promise and a subscriber queue.\nlet isRefreshing = false; let refreshSubscribers = []; function subscribeToRefresh(callback) { refreshSubscribers.push(callback); } function notifySubscribers(newToken) { refreshSubscribers.forEach(cb =\u0026gt; cb(newToken)); refreshSubscribers = []; } // Inside your HTTP interceptor (Axios, fetch wrapper, etc.) async function onRequestUnauthorized(failedRequest) { if (!isRefreshing) { isRefreshing = true; try { const newToken = await refreshAccessToken(); // POST /auth/token notifySubscribers(newToken); return retryRequest(failedRequest, newToken); } finally { isRefreshing = false; } } // Already refreshing — queue this request return new Promise(resolve =\u0026gt; { subscribeToRefresh(token =\u0026gt; resolve(retryRequest(failedRequest, token))); }); } The isRefreshing flag acts as a gate[13]. The first 401 triggers the refresh; every subsequent 401 that arrives while the refresh is running just appends a callback to the queue. When the refresh completes, notifySubscribers fires every queued callback simultaneously with the new token, and all those pending requests retry without the user seeing anything unusual[13].\nProactive refresh is even cleaner. Instead of waiting for a 401, schedule a background refresh roughly 60 seconds before the access token\u0026rsquo;s exp claim elapses. You can decode the JWT client-side (just atob the payload — no signature validation needed here, since you got it from your own server) to know exactly when it expires[8]. The refresh happens invisibly, before any request fails. The UI never stalls.\nSession Management Across Tabs and Devices Syncing State Across Browser Tabs Here\u0026rsquo;s a scenario that\u0026rsquo;s easy to overlook: a user logs out in Tab A. Tab B still has a valid in-memory access token. Until it expires, Tab B continues making authenticated requests as if nothing happened.\nThe BroadcastChannel API fixes this. It\u0026rsquo;s essentially a pub/sub channel between tabs of the same origin[10]:\nconst authChannel = new BroadcastChannel(\u0026#39;auth_events\u0026#39;); // When the user logs out: authChannel.postMessage({ type: \u0026#39;LOGOUT\u0026#39; }); // In every tab, on startup: authChannel.onmessage = ({ data }) =\u0026gt; { if (data.type === \u0026#39;LOGOUT\u0026#39;) clearSessionAndRedirectToLogin(); if (data.type === \u0026#39;TOKEN_REFRESH\u0026#39;) setAccessToken(data.token); }; Broadcasting TOKEN_REFRESH events is useful too — when one tab silently refreshes the access token, it can broadcast the new token to all other tabs. Those tabs update their in-memory token without making their own redundant refresh calls[10].\nRevoking Sessions Across Devices Multi-device session management is a different surface area. Each device holds its own refresh token. For \u0026ldquo;log out of all devices\u0026rdquo; functionality:\nTrack token families server-side. Each refresh token belongs to a lineage; rotating means each use issues a new token in the same family. Expose a /sessions endpoint listing active devices (with metadata: user-agent, last-seen IP, approximate location). Let users revoke individual sessions. On password change or security event, invalidate all refresh token families for that user at the authorization server. This instantly kills every active device session[11][12]. Most OAuth2/OIDC servers implement RFC 7009 token revocation. You just need to call it. The important detail is that revoking a refresh token should cascade — the server kills all tokens in that family, including any short-lived access tokens issued from it.\nDefending Against XSS and CSRF XSS Storing refresh tokens in HttpOnly cookies removes the biggest XSS prize — a persistent, stealable credential. But XSS can still do damage even without stealing tokens: it can make authenticated requests from within the victim\u0026rsquo;s active session. Defense-in-depth is the only honest approach.\nA strict Content Security Policy is your second line of defence[9]. The modern approach uses server-generated nonces:\nContent-Security-Policy: default-src \u0026#39;self\u0026#39;; script-src \u0026#39;nonce-{fresh-random-per-request}\u0026#39; \u0026#39;strict-dynamic\u0026#39;; object-src \u0026#39;none\u0026#39;; base-uri \u0026#39;none\u0026#39;; strict-dynamic lets your bundled scripts load dynamic code chunks (code-splitting, lazy routes) without needing to whitelist every CDN URL. nonce-{random} means only scripts the server explicitly trusts can execute — an injected \u0026lt;script\u0026gt; tag without the nonce goes nowhere[9].\nThe rest is table stakes: sanitize every piece of user-generated content before rendering (DOMPurify is the standard library for this), avoid dangerouslySetInnerHTML in React unless you\u0026rsquo;ve sanitized the input yourself, and turn on Trusted Types if your stack supports it.\nCSRF If your access token lives only in JS memory (never in a cookie), then CSRF against your API is largely a non-issue — the browser has nothing to automatically attach to cross-origin requests[4].\nBut your refresh token is in a cookie, so the token refresh endpoint needs protection. The Double Submit Cookie pattern is the OWASP-recommended approach[4]:\nGenerate a CSRF token (random string, not the refresh token itself). Store it in a non-HttpOnly cookie (so your JS can read it). Your SPA reads it and sends it as a custom request header (X-CSRF-Token) on every call to the refresh endpoint. The server validates that the header value matches the cookie value. A cross-origin attacker can\u0026rsquo;t read your cookies (same-origin policy), so they can\u0026rsquo;t forge the header value, so the forged request fails[4]. Combined with SameSite=Strict on the refresh token cookie itself — which prevents the browser from sending it on cross-site requests in the first place — you\u0026rsquo;ve covered the main CSRF vectors.\nThe BFF Pattern: When You Want Zero Token Exposure If you want the most hardened architecture possible, the Backend for Frontend (BFF) pattern removes tokens from the browser entirely[6].\nThe BFF is a thin server — a Node.js/Go/whatever process — that sits between your SPA and your APIs. It runs the full OAuth2/OIDC flow server-side, stores all tokens there, and exposes a session cookie to the browser. Your SPA makes API calls to the BFF; the BFF adds the Authorization: Bearer header before forwarding to downstream services. The browser never sees an access token[7].\nThe security upside is real: an XSS attack on your frontend can\u0026rsquo;t steal tokens that were never in the browser. The BFF can also implement DPoP (Demonstrating Proof of Possession) — RFC 9449 — to cryptographically bind access tokens to the server\u0026rsquo;s key pair, making stolen tokens useless even if intercepted in transit[1].\nArchitecture Token Exposure in Browser Complexity Best For AT in memory + RT in HttpOnly cookie AT briefly in JS memory Medium Most SPAs BFF (tokens server-side only) None Higher Enterprise, high-compliance AT + RT in localStorage Full exposure Low Never The IETF\u0026rsquo;s current browser-app security recommendations explicitly endorse the BFF pattern for high-security applications[7]. The cost is real: you\u0026rsquo;re adding a network hop and an additional service to deploy, monitor, and scale. For most apps, the in-memory AT plus HttpOnly cookie RT approach is the right tradeoff. For banking, healthcare, or anything touching regulated data, BFF removes a whole category of risk.\nA Production Checklist Getting the individual pieces right matters. Getting them right together matters more. Here\u0026rsquo;s what a properly wired SPA auth system looks like:\nAuth flow: Authorization Code + PKCE. Reject implicit flow configurations at code review. Token storage: AT in JS memory; RT in HttpOnly; Secure; SameSite=Strict cookie, scoped to /auth/token only. Token refresh: Single in-flight promise with subscriber queue; proactive refresh before expiry. Refresh token rotation: Every use of a refresh token must issue a new one and invalidate the old one. Server-side reuse detection as a tripwire. SSO: OIDC with nonce validation; implement end_session_endpoint logout — not just local state clearing. MFA: TOTP as minimum floor; WebAuthn/passkeys for phishing resistance. Verify amr claim in ID token server-side. Cross-tab sync: BroadcastChannel for logout propagation and token updates. Multi-device revocation: Server-side session tracking; expose a sessions UI; revoke all on password change. XSS defense: HttpOnly cookies + strict nonce-based CSP + sanitize all user input. CSRF defense: Double-submit cookie pattern on cookie-touching endpoints + SameSite=Strict. Nuclear option: BFF if you need zero token exposure in the browser. The piece teams consistently skip is logout — specifically the IdP session kill. Your \u0026ldquo;log out\u0026rdquo; button is security theater if it only clears local state while the SSO session at the identity provider stays alive. A complete logout revokes the refresh token at the server, clears local state, and redirects to end_session_endpoint. Three steps. All three are required.\nEnd\nSources Using OAuth for Single Page Applications — Best Practices | Curity Securing Single Page Applications with Refresh Token Rotation | Auth0 How Authentication and Authorization Work for SPAs | Okta Developer Cross-Site Request Forgery Prevention Cheat Sheet | OWASP The Developer\u0026rsquo;s Guide to JWT Storage | Descope The Backend for Frontend Pattern (BFF) | Auth0 A Guide to Backend-for-Frontend (BFF) Auth | FusionAuth What Are Refresh Tokens and How to Use Them Securely | Auth0 Mitigate Cross-Site Scripting with a Strict Content Security Policy | web.dev Comparison of Data Transfer Methods Between Browser Tabs in SPA Context | Intspirit Implementing OIDC Logout and Session Management: A Complete Guide | Logto Refresh Token Security: Best Practices for OAuth Token Protection | Obsidian Security JWT and Refresh Token Design for SPA with Multiple HTTP Requests Using Axios Interceptors | Medium OIDC Best Practices for Single-Page Applications | MojoAuth CIAM Q\u0026amp;A Passwordless Authentication in 2025: The Year Passkeys Went Mainstream | Authsignal ","permalink":"https://cloudmato.com/posts/spa-secure-authentication-sso-mfa/","title":"Secure Auth for SPAs: SSO, MFA, and Token Refresh"},{"content":"Fifty teams shipping frontend code independently sounds like a dream. Then React 17 and React 18 collide at runtime, your design system has six competing \u0026ldquo;latest\u0026rdquo; versions in production simultaneously, and a shared header owned by nobody quietly breaks for 30% of your users.\nThis is the real problem with micro frontends at scale. The pattern isn\u0026rsquo;t complicated. The governance layer — the shared kernel, versioning contracts, the theming system that can\u0026rsquo;t shatter when Team 47 deploys on a Friday — that\u0026rsquo;s where things fall apart. Here\u0026rsquo;s how to actually structure it.\nThe Monorepo Foundation First, a clarification that trips up almost every team starting this journey: monorepo is a code storage strategy; micro frontends are a runtime composition strategy. They\u0026rsquo;re not alternatives — they\u0026rsquo;re orthogonal. You can have 50 micro frontends all living in a single monorepo, which is exactly what most large-scale teams do [2]. You get team autonomy at deploy time without fragmenting shared packages across 50 different repos.\nThe real question at 50+ apps is which monorepo toolchain to use.\nTool Best For Build Speed Overhead Nx Multi-framework, enterprise guardrails, Module Federation built-in Fast (Rust daemon) Higher — generators, plugins, conventions Turborepo Pure JS/TS teams, simpler config, Vercel integration Very fast (Rust, remote cache) Low — minimal config to get started pnpm workspaces Lightweight, no orchestration needed Package manager speed Minimal — no build graph awareness Turborepo, built in Rust after Vercel acquired it in 2021, wins on simplicity [4]. But if your 50 teams span React, Angular, and Vue, use Nx — its native Module Federation support and code generators enforce conventions that prevent teams from quietly diverging from the architecture [3].\nThe workspace structure matters as much as the tool itself. Here\u0026rsquo;s a layout that scales:\n/ ├── apps/ │ ├── shell/ # Host application — routing, auth, layout │ ├── mfe-catalog/ # Owned by Catalog team │ ├── mfe-checkout/ # Owned by Checkout team │ └── ... (47 more) ├── packages/ │ ├── ui/ # Shared component library │ ├── design-tokens/ # Token definitions only — no compiled styles │ ├── utils/ # Shared utilities │ ├── i18n/ # Translation infrastructure │ └── tsconfig/ # Base TypeScript configs └── tools/ └── configs/ # Shared ESLint, Prettier, CI pipeline templates Apps depend on packages. Packages never depend on apps. Apps never directly import other apps. Cross-app imports at build time are the single most common way teams accidentally recreate a monolith inside a monorepo [15].\nThe Feature-Sliced Design architecture formalizes this boundary precisely: strict layering where lower-level shared utilities have no knowledge of the business features above them, which prevents the circular dependency chains that make large frontends unmaintainable [2].\nThe Shared Kernel: Rules, Not Suggestions The shared kernel is the non-negotiable set of things every micro frontend inherits. Discipline here is everything. Bloat the kernel and you\u0026rsquo;ve quietly rebuilt a monolith. Starve it and teams ship inconsistent UX, duplicate logic, and fight over conflicting runtime instances.\nWhat belongs in the shared kernel:\nReact (or your framework) — single instance, singleton pattern Router infrastructure (not routes — just the router setup and history object) Design tokens (CSS custom properties — more on this in a moment) Auth context / session provider i18n infrastructure Error boundary and crash reporting setup Observability hooks (analytics, tracing) What does NOT belong in the shared kernel:\nBusiness logic components or domain-specific UI Feature flags (each team manages its own flags) API clients (each MFE owns its data layer) Application-level state management The moment a product team asks to add something to the shared kernel, the platform team should ask one question: does every single micro frontend genuinely need this? If the answer is \u0026ldquo;probably yes,\u0026rdquo; it belongs. If it\u0026rsquo;s \u0026ldquo;well, our team does,\u0026rdquo; it doesn\u0026rsquo;t.\nTheming and Design Tokens at Scale This is where multi-team systems accumulate invisible technical debt. Each team ships with a slightly different shade of $blue-500. Buttons that look identical have four implementations. Dark mode works in three apps and silently breaks in twelve. Nobody notices until a designer runs an audit.\nCSS custom properties are the only theming primitive that scales across micro frontends. Not compiled SCSS variables, not JS theme objects, not Tailwind config files. CSS custom properties propagate at runtime, cross Shadow DOM boundaries, work regardless of which framework a consuming MFE uses, and can be overridden dynamically [11].\nThe token architecture needs three layers:\n/* Layer 1: Global primitives — reference only in semantic layer */ :root { --primitive-blue-500: #3b82f6; --primitive-space-4: 1rem; } /* Layer 2: Semantic tokens — these are the API consuming teams use */ :root { --color-interactive: var(--primitive-blue-500); --color-interactive-hover: var(--primitive-blue-700); --space-component-padding: var(--primitive-space-4); } /* Layer 3: Component-scoped tokens */ .button { --button-background: var(--color-interactive); background: var(--button-background); } Consuming teams should only ever reference semantic tokens. If Team 23 hardcodes --primitive-blue-500 directly, your next rebrand becomes a find-and-replace exercise across 50 micro frontends [10]. Semantic tokens decouple intent from value.\nAccessibility overlaps here directly and almost for free. Map prefers-color-scheme and prefers-reduced-motion to semantic token overrides in the token package itself:\n@media (prefers-reduced-motion: reduce) { :root { --motion-duration-standard: 0ms; --motion-duration-fast: 0ms; } } @media (prefers-contrast: more) { :root { --color-interactive: #1d4ed8; } /* Higher contrast blue */ } Any component that consumes semantic tokens gets accessibility adaptations for free — the component doesn\u0026rsquo;t need to know about the media query. This is the compounding benefit of doing tokens properly [11].\nAccessibility Cannot Be Each Team\u0026rsquo;s Problem Honest take: if you tell 50 teams \u0026ldquo;each of you is responsible for WCAG compliance,\u0026rdquo; you\u0026rsquo;ll get wildly inconsistent results. Some teams care deeply. Others have a product manager asking why they\u0026rsquo;re spending sprints on \u0026ldquo;invisible stuff.\u0026rdquo;\nAccessibility must be enforced at the shared component level, not delegated to consuming teams. Every interactive element in packages/ui ships with keyboard navigation, correct ARIA attributes, visible focus rings, and sufficient contrast — by default, not as an opt-in prop. If the accessible version is the default version, teams can\u0026rsquo;t accidentally ship an inaccessible variant without explicitly overriding it.\nThe shell application owns two accessibility concerns that live in the gaps between micro frontends:\nFocus management on route transitions — not just scroll-to-top. When the user navigates from the Catalog MFE to the Checkout MFE, a screen reader needs to hear that the page changed. The shell should announce the new page title to an ARIA live region on route change. Skip navigation — the shell injects \u0026lt;a href=\u0026quot;#main-content\u0026quot; class=\u0026quot;skip-link\u0026quot;\u0026gt;Skip to main content\u0026lt;/a\u0026gt; once. Every MFE must expose a #main-content landmark. This is a published interface contract, not a nicety. The skip link contract belongs in your ADR (Architecture Decision Record) and should be checked by the contract tests in CI. If a team removes the #main-content id from their MFE, the CI pipeline catches it before it ships.\nRuntime Integration: Three Real Options Module Federation 2.0 Webpack Module Federation allows independently built applications to expose and consume modules at runtime without pre-bundling them together [1]. Version 2.0 reached a stable release in April 2026 and added three things that genuinely matter at 50+ MFE scale [7]:\nTypeScript type sharing — remotes export their types to the host, so you get autocomplete across MFE boundaries without manual type package publishing Manifest-based remote discovery — the host fetches a manifest at startup to resolve remote URLs dynamically, enabling canary deploys and instant rollbacks without redeploying the shell Bundler-agnostic runtime — @module-federation/enhanced works across webpack, Rspack, Rollup, and Vite under a single API For teams where webpack build times have become a genuine bottleneck, Rspack (webpack-compatible, built in Rust) + MF2 cuts build time by 5–10× while maintaining full API compatibility [7].\nImport Maps Import maps are a browser-native feature that lets you remap bare module specifiers to real URLs — ~94.5% global browser support as of early 2026 [8]:\n\u0026lt;script type=\u0026#34;importmap\u0026#34;\u0026gt; { \u0026#34;imports\u0026#34;: { \u0026#34;react\u0026#34;: \u0026#34;https://cdn.example.com/react@18.3.0/esm.js\u0026#34;, \u0026#34;@company/design-tokens\u0026#34;: \u0026#34;https://cdn.example.com/tokens@2.4.0/index.js\u0026#34; } } \u0026lt;/script\u0026gt; The shell controls the import map. The shell controls which version every MFE gets. No negotiation, no runtime version conflicts. The tradeoff: import maps are harder to combine with SSR and don\u0026rsquo;t offer the DX niceties of Module Federation type sharing. Single-spa\u0026rsquo;s recommended setup has used SystemJS + import maps as its standard since 2022, making it the well-tested path for gradual modernization workloads [16].\nNative Federation Native Federation applies the Module Federation mental model to browser-native ESM and import maps — no webpack dependency required [9]. It\u0026rsquo;s the natural choice if you\u0026rsquo;re building new MFEs with Vite, Rspack, or Angular 17+ and want to avoid bundler lock-in entirely.\nFor new systems in 2026: Native Federation or MF2-on-Rspack. For migrating existing webpack setups: @module-federation/enhanced is the path of least resistance.\nZero Version Conflicts: The Singleton Contract Here\u0026rsquo;s the thing about version conflicts that the docs undersell: the problem isn\u0026rsquo;t just duplicated code (annoying but survivable) — it\u0026rsquo;s two separate React instances running simultaneously. React hooks throw when a component from one instance tries to use context from another [6]. Zalando\u0026rsquo;s engineering team called this out explicitly as a \u0026ldquo;real concern\u0026rdquo; when building their modular portal with Module Federation, noting it could cause silent runtime errors or unexpected behavior across teams [12].\nThe fix is the singleton flag in your shared config:\n// Every MFE\u0026#39;s webpack/rspack config — no exceptions new ModuleFederationPlugin({ name: \u0026#39;mfe_catalog\u0026#39;, shared: { react: { singleton: true, strictVersion: false, requiredVersion: \u0026#39;^18.0.0\u0026#39; }, \u0026#39;react-dom\u0026#39;: { singleton: true, strictVersion: false, requiredVersion: \u0026#39;^18.0.0\u0026#39; }, \u0026#39;@company/design-tokens\u0026#39;: { singleton: true, strictVersion: true, // ← fail loudly on breaking version requiredVersion: \u0026#39;~2.0.0\u0026#39; }, } }) singleton: true tells Module Federation to use the first-loaded instance and skip loading any alternative [5]. For React and ReactDOM, pair it with strictVersion: false — one running instance matters more than version exactness. For design tokens, strictVersion: true — you want it to throw at startup if a team ships on a breaking version, because silent inconsistency is worse than a loud failure [5].\nThe multiple instance problem is exactly this — two separate runtime instances of the same library coexisting and causing unpredictable behavior [6]. Enforcing this shared config across all 50 apps via a linting rule or a generated Nx plugin config (rather than trusting teams to copy-paste it correctly) is the only sustainable approach at scale.\nPublishing Strategy At 50+ MFEs, you\u0026rsquo;re managing two distinct publishing concerns that need separate strategies.\nShared packages (packages/ui, packages/design-tokens, packages/utils) are consumed by other teams and published to a private npm registry. These get semantic-release configured per-package, which automates version bumps, changelogs, and registry publishing entirely from conventional commit messages [13]. No manual version bumps. No \u0026ldquo;I forgot to publish after merging.\u0026rdquo; The multi-semantic-release package extends this to the monorepo — if packages/ui has a breaking change, any package depending on it automatically gets a patch bump in the same release run [13].\nMFE apps are not npm packages. They\u0026rsquo;re deployed artifacts. The CDN URL structure matters:\nhttps://cdn.company.com/mfe/ ├── catalog/ │ ├── v2.4.1/remoteEntry.js # pinned version, immutable │ ├── v2.4.0/remoteEntry.js │ └── manifest.json # { \u0026#34;stable\u0026#34;: \u0026#34;v2.4.1\u0026#34;, \u0026#34;canary\u0026#34;: \u0026#34;v2.5.0-rc1\u0026#34; } ├── checkout/ │ └── ... └── global-manifest.json # single source of truth for shell at startup The shell must never hardcode latest/ URLs in production. At startup, the shell fetches global-manifest.json and resolves the correct remote URL for each MFE [18]. This is what enables canary deployments (route 5% of traffic to a new version), instant rollbacks (update the manifest, not a shell deploy), and stable pinned versions for debugging production incidents.\nCI/CD: Independence Without Chaos Each micro frontend gets its own pipeline, but the pipelines share a contract [14]. Every MFE CI pipeline runs these steps in order:\nBuild — generate remoteEntry.js + hashed static assets Unit + integration tests — scoped to the MFE only Contract tests — assert that all exposed module interfaces still match the TypeScript contract the shell expects Upload versioned artifacts to CDN (immutable, never overwrite) Update manifest — register as a canary candidate Monitor canary — 5% traffic, watch error rate for 15 minutes Promote to stable or rollback in manifest The contract testing step is what most teams skip and then regret. When the Catalog MFE renames a prop on its exposed CatalogPage component, the shell doesn\u0026rsquo;t find out until a user sees a broken page [14]. A contract test that checks the exposed module\u0026rsquo;s shape against the host\u0026rsquo;s expectations — committed in the same repo, run in both the MFE\u0026rsquo;s CI pipeline and the shell\u0026rsquo;s — closes this gap entirely.\nThe monorepo pays off in CI: Nx\u0026rsquo;s affected command or Turborepo\u0026rsquo;s dependency graph-aware --filter automatically identifies which apps to rebuild and test when a shared package changes [3][4]. A change to packages/design-tokens triggers the pipelines for every MFE that consumes it — you get broad test coverage without running everything on every commit.\nGovernance: Who Owns What At 50 teams, \u0026ldquo;everyone owns the shared components\u0026rdquo; means nobody does. The ownership model needs to be explicit.\nA dedicated platform/foundation team owns:\npackages/ui — the shared component library packages/design-tokens — the token definitions The shell application\u0026rsquo;s routing and layout infrastructure The global CDN manifest The Module Federation / import map configuration templates Each product team owns:\nIts own MFE app, end-to-end Its own CDN deployment pipeline Keeping its shared dependency versions within the permitted semver range An Architecture Working Group (doesn\u0026rsquo;t need to be formal — a weekly async thread with opt-in attendance works fine) handles cross-cutting decisions: proposals for new shared packages, deprecations, token naming conventions, breaking change schedules.\nAdding a new shared package requires a proposal, not just a PR. The worst governance failure pattern I\u0026rsquo;ve seen in large front-end platforms: a team adds a new packages/feature-x to the monorepo unilaterally, other teams start depending on it, and then the original team reorganizes — leaving the platform team with an undocumented, orphaned package that everyone secretly imports. One short RFC document, one approval from the platform team, is all it takes to prevent this.\nThe platform team should be the only group with write access to the global manifest and the CDN stable promotion pipeline. Every other team can deploy to canary freely — but promoting to stable goes through one gatekeeper. This is the difference between \u0026ldquo;50 teams can ship independently\u0026rdquo; and \u0026ldquo;50 teams can accidentally take down the shell on a Friday afternoon.\u0026rdquo;\nEnd\nSources Module Federation — webpack official docs Monorepo Architecture: The Ultimate Guide for 2025 — Feature-Sliced Design Micro Frontend Architecture — Nx Documentation Monorepo Tools Comparison: Turborepo vs Nx vs Lerna in 2025 Solving Micro-Frontend Challenges with Module Federation — LogRocket Blog Understanding the Multiple Instance Problem in Micro Frontends Module Federation 2.0 Reaches Stable Release with Wider Support — InfoQ Micro Frontends with Import Maps: The Lightweight Alternative to Module Federation Micro Frontends with Angular and Native Federation — Angular Blog Design Tokens: The Foundation of Your UI Architecture — Feature-Sliced Design CSS Variables Guide: Design Tokens \u0026amp; Theming — FrontendTools Building a Modular Portal with Webpack Module Federation — Zalando Engineering Blog Semantic Release — FAQ and Documentation CI/CD Pipelines and Automation for Micro-Frontends — microfrontend.dev Micro-Frontends: Are They Still Worth It in 2025? — Feature-Sliced Design The Recommended Setup — single-spa Micro-Frontends: Complete Implementation Guide for Large Teams Scalable Micro-Frontend Deployment Strategy ","permalink":"https://cloudmato.com/posts/micro-frontend-architecture-50-teams/","title":"50+ Micro Frontends: Monorepo, Runtime Integration \u0026 Zero Conflicts"},{"content":"The cable behind your monitor has a more dramatic history than most people realize. Every decade or so, some group of companies decides the current standard is holding them back, invents something new, and the industry spends the next ten years transitioning — leaving everyone with a drawer full of adapters they\u0026rsquo;ll never use again. Here\u0026rsquo;s why that happened, and why it\u0026rsquo;ll probably keep happening.\nThe Analog Jungle: Composite, S-Video, and Component Before there was HDMI or DisplayPort, there was a mess of analog standards that ran televisions and early home electronics for decades. Each one existed because the previous one was genuinely not good enough.\nComposite video is where this all starts. When the United States introduced color television in 1954, engineers faced a real constraint: how do you broadcast color without breaking every black-and-white TV already in someone\u0026rsquo;s living room? The solution was to multiplex everything — brightness (luminance), color (chrominance), and sync signals — into a single combined signal [2]. That\u0026rsquo;s composite video. The iconic yellow RCA connector on the back of every VCR, PlayStation 2, and budget television set you ever owned? That\u0026rsquo;s your composite connection carrying all of that mashed-together information.\nIt worked. For decades, it was good enough. But combining luminance and chrominance into a single signal causes interference between them. The visual result is color bleeding, smearing, and a particular artifact called \u0026ldquo;dot crawl\u0026rdquo; — that shimmering distortion you\u0026rsquo;d notice on fine patterns like a striped shirt on TV. Engineers knew this was fundamentally a design flaw, not something you could fix by tweaking the signal.\nS-Video (Separate Video) was the answer. By splitting luma and chroma onto two separate pins within the same 4-pin mini-DIN connector, it eliminated the crosstalk that caused dot crawl [3]. The concept was developed in the late 1970s but didn\u0026rsquo;t find wide consumer adoption until JVC introduced the S-VHS format in 1987 and bundled the connector with it. If you ever connected a higher-end VCR or a capture card to a computer in the early 2000s using S-Video instead of composite, you\u0026rsquo;d have seen a noticeably cleaner image — colors stayed within their edges, fine detail was crisper.\nBut S-Video still topped out at standard definition. As high-definition television started materializing in the 1990s, neither format could carry the signal bandwidth required. Component video (those three RCA jacks — typically color-coded red, green, blue but actually carrying a Y/Pb/Pr luma-difference signal) solved the bandwidth problem by splitting the signal across three separate cables entirely [1]. DVD players, cable boxes, and game consoles like the PS2 and original Xbox used component extensively. It was the best analog video quality available to consumers before the digital era fully arrived — capable of carrying 1080i and even some 1080p signals cleanly.\nThen computers showed up and created an entirely separate problem.\nVGA: IBM\u0026rsquo;s Accident That Lasted 30 Years In 1987, IBM introduced the Video Graphics Array connector alongside its PS/2 line of computers [4]. The 15-pin D-sub connector supported resolutions up to 640×480 pixels. For 1987, that was impressive. Nobody in the room that day was thinking \u0026ldquo;this will still be on laptops in 2013.\u0026rdquo;\nVGA is purely analog. Your graphics card generates a digital signal internally, converts it to analog voltage, sends it down the cable, and your monitor converts it back to digital to display it on the LCD panel. Every analog-to-digital conversion introduces noise and imprecision. At low resolutions on CRT screens that were themselves analog, this was invisible. At 1920×1200 on an LCD panel, it produced the characteristic soft-focus blur that anyone who\u0026rsquo;s connected a laptop to a projector via VGA will remember.\nSo why did it survive so long? A few reasons. It was cheap to implement. It was on everything. Replacing it required everyone to simultaneously agree on something new, and the corporate IT world — projectors, conference rooms, desk monitors — had zero urgency to move [17]. Intel and NVIDIA officially dropped hardware VGA support from their chipsets in the early 2010s, but the connectors kept appearing on budget laptops and AV equipment for years after that [4].\nVGA\u0026rsquo;s fundamental problems weren\u0026rsquo;t solvable by incremental improvement:\nAnalog only — incompatible with modern digital LCD and OLED panels without conversion No audio — always required a separate cable No content protection — HDCP wasn\u0026rsquo;t possible over an unencrypted analog signal No HDR or color metadata — the signal carries no information about color space or dynamic range Physical size — 15 fragile pins in a wide D-sub shell, impractical for slim devices Something had to replace it.\nDVI: The Almost-Revolution That No One Loved By the late 1990s, LCD monitors were rapidly displacing CRTs. The problem was stark: LCD panels are digital by nature, but VGA was feeding them an analog signal that the monitor then had to digitize again. More conversions, more signal degradation, especially at higher resolutions where every pixel matters.\nIn 1999, the Digital Display Working Group — a consortium including Intel, IBM, HP, Compaq, Fujitsu, Silicon Image, and NEC — released the Digital Visual Interface (DVI) standard [5]. It was the first digital video connection for consumer computers. A pure digital bitstream from the GPU to the display, no analog conversion in the path. The image sharpness difference was real and visible.\nDVI came in multiple flavors (DVI-D for digital only, DVI-A for analog only, DVI-I for both, and dual-link versions that doubled the bandwidth), which already made it more confusing than it needed to be. But the bigger issues were elsewhere.\nNo audio whatsoever. VGA didn\u0026rsquo;t carry audio either, but by 1999 this was an increasingly large omission. The connector was enormous. That wide rectangular plug with the cross-shaped pin array was practical on a desktop but laughable on anything portable. Content protection was absent. DVI could transmit an entirely unencrypted digital signal — meaning a perfect, lossless digital copy of video was technically trivial to make. Hollywood studios saw this and panicked [6]. The thumbscrews were a nightmare. They hooked on nearby cables when you moved equipment. This sounds minor. It\u0026rsquo;s not. No 4K. Even dual-link DVI maxes at 2560×1600. By 2008, DVI was already fading from the market [6]. Intel and AMD formally announced in 2010 that they\u0026rsquo;d phase out DVI support by 2015 in favor of HDMI and DisplayPort. DVI had solved the core analog problem — digital signal end-to-end — but everything around that core was wrong. It took two successors, coming from two completely different directions, to actually replace it.\nHDMI: When Hollywood Joined the Standards Committee Here\u0026rsquo;s something most people don\u0026rsquo;t know about HDMI: the content industry was a driving force behind its design, not just the electronics manufacturers.\nSeven companies — Hitachi, Panasonic, Philips, Silicon Image, Sony, Thomson, and Toshiba — started developing the High-Definition Multimedia Interface (HDMI) specification in April 2002. The spec was finalized by December 2002, and first consumer products arrived in 2003 [7].\nThe engineers wanted a cleaner replacement for the tangle of component cables in living rooms. The studios wanted HDCP (High-bandwidth Digital Content Protection) baked into the standard at the hardware level so that encrypted premium content couldn\u0026rsquo;t be copied at the connector. HDMI delivered both. That\u0026rsquo;s why studios and content platforms immediately mandated HDMI support — Blu-ray players, streaming boxes, and TVs all had it from early on [9].\nThe other killer feature was simpler: one cable carries both video and audio. Component video for HD picture, separate optical or RCA cables for sound, SCART adapters in Europe — all of it went away. One cable, everything.\nHDMI has been through a lot of versions since then:\nVersion Year Bandwidth Key Capability 1.0 2002 4.95 Gbps 1080p, 8-channel audio 1.3 2006 10.2 Gbps xvYCC wide color, Dolby TrueHD 1.4 2009 10.2 Gbps 4K@30Hz, Audio Return Channel, 3D 2.0 2013 18 Gbps 4K@60Hz, Rec.2020 color space 2.0a 2015 18 Gbps HDR (High Dynamic Range) 2.1 2017 48 Gbps 4K@120Hz, 8K@60Hz, Variable Refresh Rate [8]\nOne thing HDMI never became: free. Manufacturers who adopt HDMI pay annual licensing fees of $5,000–$10,000 plus $0.04–$0.15 per device shipped [10]. For consumer electronics companies selling televisions in the millions, this is manageable. For PC graphics card and monitor manufacturers — who care about cost-per-unit much more tightly — it was irritating. Which is why DisplayPort exists.\nDisplayPort: The Port That Said \u0026ldquo;No Licensing Fees\u0026rdquo; VESA (the Video Electronics Standards Association) published DisplayPort 1.0 in May 2006 [11]. The explicit goal was to replace VGA and DVI in the PC monitor space, and to do it without burdening manufacturers with per-device royalties.\nDisplayPort is a royalty-free open standard. No annual fees, no per-unit costs. That\u0026rsquo;s the single most important thing to understand about why it took hold in the PC industry while HDMI dominated the living room [10].\nTechnically, it\u0026rsquo;s also architecturally different from HDMI in a meaningful way. HDMI uses a traditional parallel signal approach. DisplayPort uses packet-based data transfer — similar in concept to how network protocols work — which makes it more scalable. Adding bandwidth in future versions is a matter of adding lanes or increasing per-lane speeds, not redesigning the encoding scheme from scratch.\nDisplayPort 1.2, released in 2010, added a genuinely useful feature HDMI has never properly matched: daisy-chaining. A single DisplayPort output could drive one monitor, which then passed the signal to a second monitor, and so on — no hub required, no extra GPU outputs needed.\nDisplayPort 2.0 (2019) and 2.1 (2022) pushed bandwidth to 80 Gbps — nearly double HDMI 2.1\u0026rsquo;s 48 Gbps at the time. That\u0026rsquo;s enough for a single 16K display, or three 4K displays at 144Hz simultaneously from one port [1].\nThe practical split ended up being: DisplayPort for gaming monitors, professional workstations, and PC graphics cards; HDMI for televisions, projectors, and consumer AV gear. Both are still around, both are still being actively developed.\nUSB-C and Thunderbolt: One Port to Rule (Almost) Everything Around 2014, the industry started asking a different question. Laptops were getting thinner. Every port was expensive chassis real estate. What if you didn\u0026rsquo;t need a dedicated video output port at all?\nThe answer was USB-C Alt Mode. USB-C is a connector shape — not a protocol — and Alt Mode is the mechanism that lets a USB-C port completely reassign its internal high-speed lanes to carry a different signal entirely [12]. DisplayPort Alt Mode, introduced in 2014, made it possible to route a DisplayPort signal through a USB-C connector. Suddenly, a single USB-C cable could simultaneously carry video to a display, USB data for peripherals, and up to 100W of charging power [12].\nThis sounds complicated — and honestly, it is, which is why some USB-C ports support video output and others don\u0026rsquo;t. The physical connector looks identical either way.\nThen Thunderbolt entered the picture. Intel and Apple had been co-developing Thunderbolt since 2011, originally over a Mini DisplayPort connector. Thunderbolt 3, released in 2015, moved to USB-C and offered 40 Gbps of bidirectional bandwidth — carrying PCIe data, DisplayPort video, and USB data simultaneously [13]. It could drive dual 4K monitors or a single 5K display from a single port. One cable to your desk, and your laptop suddenly had monitors, peripherals, power, and fast storage.\nIn 2019, Intel contributed the Thunderbolt 3 specification to the USB standards body, which led directly to USB4 — making similar capabilities available royalty-free to any manufacturer, a move that significantly accelerated adoption.\nThunderbolt 5, announced in 2023 with shipping products through 2024, doubled the baseline again: 80 Gbps standard bandwidth with a \u0026ldquo;Bandwidth Boost\u0026rdquo; mode that can push 120 Gbps when you\u0026rsquo;re primarily sending data in one direction [15]. It carries DisplayPort 2.1 at the full UHBR20 spec, which means it can drive three 4K displays at 144Hz each, or a single 8K display at 120Hz, from one USB-C port [13].\nWhat\u0026rsquo;s Coming Next This is where it gets interesting. Both HDMI and DisplayPort are actively pushing their next generations, and the numbers involved feel almost absurd for anything you\u0026rsquo;d actually plug into right now.\nHDMI 2.2 was formally announced and doubles HDMI 2.1\u0026rsquo;s bandwidth to 96 Gbps [14]. What does that enable? Theoretically: 8K at 240Hz, 10K at 120Hz, or 4K at 480Hz. The first HDMI 2.2-certified products are expected to reach shelves before the end of 2026, though the devices that actually operate at the full 96 Gbps spec are pushed to 2027 [16]. The initial 2026 wave focuses on implementing a new Latency Indication Protocol (LIP) for better audio-video sync between displays and soundbars — a real-world problem that\u0026rsquo;s surprisingly annoying [16].\nDisplayPort 2.1b takes a deliberately different approach. Rather than chasing raw bandwidth, it focuses on cable practicality [14]. High-bandwidth DisplayPort cables (UHBR20, the 80 Gbps spec) were previously limited to about 1 meter for passive cables — which is fine for a monitor on a desk, but terrible for VR headsets tethered to a PC across a room, or any setup where the GPU isn\u0026rsquo;t immediately adjacent to the display. DisplayPort 2.1b introduces a new active cable design that extends that range to 3 meters at full bandwidth. For gaming setups and professional workstations, this is a more meaningful upgrade than the next resolution tier most people can\u0026rsquo;t perceive anyway.\nThe Nvidia RTX 50 series cards, shipping in 2025 and 2026, support DisplayPort 2.1 at UHBR20, and one major TV manufacturer (Hisense) is adding DisplayPort input to their 2026 lineup — a USB-C port with full DisplayPort support, making it the first major TV to support the standard natively.\nWhat about wireless? Honestly, it\u0026rsquo;s not replacing cables for high-performance use anytime soon. Miracast, the Wi-Fi Alliance\u0026rsquo;s wireless display standard from 2012, supports up to 1080p via Wi-Fi Direct. Wi-Fi 6E and 7 have the theoretical bandwidth to push higher resolutions. But latency is the killer — competitive gaming and professional video production need sub-millisecond precision that radio transmission can\u0026rsquo;t consistently guarantee. Wireless stays a convenience feature for casual screen mirroring, not a cable replacement.\nThe broader trend is clear though. USB-C with Thunderbolt or USB4 is becoming the universal physical connector for laptops, portable displays, and docking stations. HDMI stays on TVs — the licensing economics work there and the installed base is enormous. DisplayPort stays dominant on gaming monitors, graphics cards, and workstations. But for anything portable, the industry is converging on \u0026ldquo;one USB-C port, everything flows through it.\u0026rdquo;\nWhether that\u0026rsquo;s actually simpler than the old world of dedicated ports is debatable. You now get a single connector that might or might not support video output, might or might not be Thunderbolt, might or might not have enough lanes for USB data and DisplayPort simultaneously — depending entirely on the device you bought. The adapter drawer is still full. It just has a different shape of adapter in it now.\nEnd\nSources History of Display Interfaces — XDA Developers Composite Video — Wikipedia S-Video — Wikipedia What is VGA? Comprehensive Guide — HP Tech Takes Digital Visual Interface — Wikipedia Obsolete Technology: DVI — Solid Signal Blog HDMI History and Versions Guide — ViewPlayTek What is HDMI? — Cable Matters The Sordid History of HDMI — Solid Signal Blog Why DisplayPort — VESA History of DisplayPort Technology — CableWholesale USB-C DisplayPort Alt Mode Explained — BenQ Thunderbolt 5 — What You Need to Know — Kensington HDMI 2.2 vs. DisplayPort 2.1b Explained — PCWorld Thunderbolt 5 for Gaming — Intel HDMI 2.2 Devices Arrive This Year — Guru3D VGA Ports Bowing Out of Home Computers — Computerworld ","permalink":"https://cloudmato.com/posts/history-of-video-output-ports/","title":"Video Output Ports History: VGA, HDMI, DisplayPort \u0026 USB-C"},{"content":"It\u0026rsquo;s 3am. PagerDuty is screaming. Checkout is failing for some customers but not others. You SSH into a box, tail the logs, and you\u0026rsquo;re greeted with a wall of INFO: processing request lines with no order ID, no user ID, no trace of which downstream service choked. Now you\u0026rsquo;re not debugging — you\u0026rsquo;re archaeology.\nI\u0026rsquo;ve been on the other side of that night more times than I\u0026rsquo;d like to admit. And honestly, the difference between a 5-minute fix and a 3-hour outage is almost never the bug itself. It\u0026rsquo;s whether your logs told you anything useful. So let\u0026rsquo;s talk about what good logging actually looks like — not the textbook \u0026ldquo;log everything\u0026rdquo; advice, but the stuff that saves you when production is on fire.\nWhy most logging is useless when you need it most Here\u0026rsquo;s the uncomfortable truth: most teams don\u0026rsquo;t log the wrong amount, they log the wrong things in the wrong way. Without a clear strategy, logs become noisy, unstructured, expensive, and nearly useless during an incident [1]. You end up paying a fortune to store gigabytes of user clicked button and then can\u0026rsquo;t find the one line that explains why a payment failed.\nA log line exists for exactly one reason: so future-you, mid-incident, can reconstruct what happened without reproducing it. That\u0026rsquo;s the test. If a log doesn\u0026rsquo;t help you answer \u0026ldquo;what was the system doing, for whom, and what went wrong,\u0026rdquo; it\u0026rsquo;s noise. Logs should provide enough context to understand an event without needing to reproduce the issue — timestamps, clear error messages, and unique identifiers to trace across systems [1].\nKeep that one sentence in your head and most of the decisions below become obvious.\nLog levels: stop using them as decoration Almost everyone gets log levels slightly wrong. They sprinkle logger.info and logger.debug around based on vibes, and then in production everything is either silent or a firehose. Log levels are a severity contract, not a style choice. Get the contract right and you can filter signal from noise instantly.\nThe standard hierarchy, lowest to highest priority [4]:\nLevel When to use it Should it page someone? TRACE Ultra-fine-grained flow, per-loop iteration. Rarely on in prod. No DEBUG Variable states, API payloads, execution branches — developer diagnostics [2] No INFO Normal runtime events: startup, shutdown, user actions, state changes No WARN Something\u0026rsquo;s off but the system recovered or degraded gracefully Maybe (trend) ERROR An operation failed, but the app keeps running [3] Often FATAL Unrecoverable — app is about to exit (missing config at boot, OOM) [3] Yes A few opinions I\u0026rsquo;ll die on:\nINFO is your production default. Set the threshold to INFO and you capture INFO, WARN, ERROR, FATAL while dropping DEBUG noise [5]. That\u0026rsquo;s the right baseline for almost every service. DEBUG is for transient debugging, then turn it off. Writing extensive DEBUG logs adds real I/O overhead that slows high-throughput systems and inflates latency [1]. Don\u0026rsquo;t leave it on \u0026ldquo;just in case.\u0026rdquo; WARN is the most abused level. A retry that succeeded is a WARN. A failed request the user actually saw is an ERROR. If you can\u0026rsquo;t tell the difference between \u0026ldquo;we handled it\u0026rdquo; and \u0026ldquo;the customer got a 500,\u0026rdquo; your alerts are lying to you. The ability to flip DEBUG on for a single service at runtime — without a redeploy — is worth its weight in gold. That\u0026rsquo;s the legitimate use of DEBUG in prod: turn it on for ten minutes, capture the weird case, turn it back off. Get this right and your on-call dashboard becomes \u0026ldquo;show me all ERROR and FATAL in the last 15 minutes\u0026rdquo; — a query that actually means something.\nWhat to actually log (and what to never log) The fields every log line should carry If you take one thing from this article, make it this: stop logging strings, start logging structured key-value records. Structured logging emits machine-readable key-value pairs (typically JSON) instead of free text, so each field is independently queryable for filtering and correlation across services [12]. The difference at query time is night and day — grep versus an actual WHERE order_id = '...'.\nEvery production log line should carry, at minimum [12]:\ntimestamp — ISO 8601, in UTC. Always UTC. Timezone bugs during an incident are their own special hell. level — the severity from above service — which service emitted it event — a short, consistent name like payment.captured or order.rejected trace_id and request_id — the correlation glue (more on this next) domain fields — user_id, order_id, tenant_id — whatever lets you slice by who and what Here\u0026rsquo;s the same failure logged two ways. Unstructured:\nERROR Payment failed for user after timeout Structured:\n{ \u0026#34;timestamp\u0026#34;: \u0026#34;2026-06-20T15:42:11.204Z\u0026#34;, \u0026#34;level\u0026#34;: \u0026#34;ERROR\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;payments-api\u0026#34;, \u0026#34;event\u0026#34;: \u0026#34;payment.capture_failed\u0026#34;, \u0026#34;trace_id\u0026#34;: \u0026#34;4bf92f3577b34da6a3ce929d0e0e4736\u0026#34;, \u0026#34;request_id\u0026#34;: \u0026#34;req_8821aa\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;usr_4471\u0026#34;, \u0026#34;order_id\u0026#34;: \u0026#34;ord_99812\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;stripe\u0026#34;, \u0026#34;error_code\u0026#34;: \u0026#34;gateway_timeout\u0026#34;, \u0026#34;duration_ms\u0026#34;: 30021 } The first one tells you something broke. The second one tells you payments-api timed out talking to Stripe after 30 seconds for a specific order, and lets you instantly find every other request that hit the same gateway timeout. One of these survives a 3am incident.\nOne more thing on field names: pick a convention and never break it. If it\u0026rsquo;s user_id in one service, don\u0026rsquo;t make it userId or user in another [4]. I\u0026rsquo;ve wasted real time joining logs across services that disagreed on casing. Stick to snake_case, write it in a shared logging library, and stop relying on humans to remember.\nLog errors as structured data, not stack-trace soup Don\u0026rsquo;t dump a raw stack trace as a giant string field. Log structured error information — error type, message, code, and the stack as its own field [12]. A stringified stack trace is unsearchable and bloats your storage. Structured error fields let you ask \u0026ldquo;how many gateway_timeout errors in the last hour\u0026rdquo; without regex gymnastics.\nWhat to never log This is where carelessness turns into a breach or a compliance fine. Never log secrets or PII. Usernames, passwords, security questions, API keys, access tokens, private keys, and session IDs should never hit a log [13][16]. Same for personally identifiable information — names, addresses, government IDs, full payment details [17].\nAnd here\u0026rsquo;s the part people get wrong: don\u0026rsquo;t rely on developers to remember to redact. Someone always forgets. The right approach is automated filters that strip secrets before they\u0026rsquo;re written, plus an allowlist — explicitly define the non-sensitive fields you\u0026rsquo;re allowed to include, rather than trying to blocklist every sensitive one [16][17]. Allowlists fail safe; blocklists fail open.\nThe OWASP Logging Cheat Sheet is the canonical reference here, and it\u0026rsquo;s worth a read if you\u0026rsquo;re handling anything regulated [15].\nCorrelating across services: the part that actually matters Here\u0026rsquo;s where logging in a distributed system gets genuinely hard. In a monolith, one request = one thread = one continuous log stream. In microservices, a single user action might touch six services, two queues, and a background job — and each one logs to its own stream. Without a thread connecting them, you\u0026rsquo;ve got six separate murder mysteries instead of one.\nThe thread is a correlation ID (or trace ID): a unique identifier attached to a request that travels with it across every service boundary [9]. When a request enters your system, it gets an ID. Every service extracts that ID from the incoming request and passes it along in the headers of any downstream calls [10]. Then every log line includes it. Now you can pull every log across every service for that one request with a single query.\nDon\u0026rsquo;t invent your own header — use W3C Trace Context You could roll your own X-Correlation-ID header, and plenty of teams do. But there\u0026rsquo;s a standard now, and it\u0026rsquo;s worth adopting. The W3C Trace Context spec defines a traceparent header that everything understands [13]. Its format is dead simple:\ntraceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 │ │ │ │ version trace-id (32 hex) parent span-id flags The trace ID identifies the whole request journey; the span ID identifies one operation within it [14]. Use this and any compliant tool — Jaeger, Zipkin, Sentry, Datadog — can stitch your trace together without custom glue [10].\nLogs vs traces — they\u0026rsquo;re cousins, not twins People conflate these, so let me be blunt about the difference:\nA trace is the structured timeline of a request across services, made of nested spans, each span being one operation with a start, end, and duration. A log is a point-in-time event with context. The magic happens when you put the trace_id into your log lines. Then you can jump from a slow span in your trace view straight to the exact logs emitted during that span. That\u0026rsquo;s the whole game of modern distributed tracing [8].\nOpenTelemetry is the tool I\u0026rsquo;d reach for here. Its SDKs use W3C Trace Context as the default propagation format, so if you instrument with OTel you get cross-service correlation basically for free [13]. No need to hand-thread headers through every HTTP client.\nA practical tip: thread-local context In most frameworks you don\u0026rsquo;t want to pass the correlation ID through every function signature manually — that\u0026rsquo;s miserable. Use a context mechanism: MDC (Mapped Diagnostic Context) in the Java/SLF4J world, context.Context in Go, async local storage in Node. You set the trace ID once when the request arrives, and the logging framework automatically stamps it on every line for the rest of that request [10]. Set it in middleware, forget about it.\nThe canonical log line: my favourite underrated pattern This one changed how I think about logging, and it comes from Stripe\u0026rsquo;s engineering blog. The idea: alongside your normal scattered logs, emit one fat, structured log line at the end of every request that contains all the important telemetry in one place [20].\nSo at the end of a request you emit a single line carrying: the route, the user ID, the response status, total duration, which downstream services were called and how long each took, the auth method, the trace ID — everything that matters about that request, colocated.\nWhy is this so good? Because during an incident, you write one query against one line instead of piecing together five separate log statements [20]. The community has rediscovered this exact idea under the name wide events — one comprehensive event per request with every attribute attached [20]. Same concept, and it\u0026rsquo;s the direction observability is heading.\nThe clever implementation detail from Stripe: they wrap the emission in an ensure block (a finally, basically) so the canonical line gets written even if the request threw an exception mid-flight [20]. Here\u0026rsquo;s the shape of it:\ndef handle_request(req): ctx = {\u0026#34;request_id\u0026#34;: req.id, \u0026#34;route\u0026#34;: req.route, \u0026#34;user_id\u0026#34;: req.user_id} try: result = process(req) ctx[\u0026#34;status\u0026#34;] = result.status return result finally: ctx[\u0026#34;duration_ms\u0026#34;] = elapsed_ms() logger.info(\u0026#34;canonical_line\u0026#34;, **ctx) # always runs Do regular logging and a canonical line per request. The regular logs give you the play-by-play; the canonical line gives you the box score.\nKeeping logs affordable: sampling without going blind Logs get expensive fast, and the naive reaction — \u0026ldquo;just log less\u0026rdquo; — usually means you turn off the thing you needed. The smarter move is sampling: keep only part of the stream, but be deliberate about which part [18].\nThe strategy I\u0026rsquo;d recommend for high-volume systems [18][19]:\nNever sample errors and warnings. They\u0026rsquo;re rare and high-value. Keep 100% of them. Sample the boring stuff aggressively — health checks, successful auth, routine 200s. Keeping 1 in 100 still shows you clear traffic patterns [18]. Always record the sample rate in the log itself, so your analysis can multiply back up and not lie to you about volumes [18]. Tier your storage — hot/searchable for recent logs, cheap cold archive for older ones. Structured logs make this easy because every line has queryable metadata to route on [12]. Datadog, Better Stack and others have good deep-dives on tuning this, but the principle holds everywhere: sample noise, never sample signal, and always know your rate.\nAlso — boring but it bites people — rotate and archive your logs. Unbounded log files fill disks and take down the very service you were trying to observe [1]. Set rotation, set retention, move on.\nA quick gut-check list Before you call your logging \u0026ldquo;done,\u0026rdquo; run through this:\nAre logs structured JSON with consistent field names? [12] Does every line carry a trace_id that propagates across services? [13] Is INFO your prod default, with DEBUG flippable at runtime? [5] Do ERROR and FATAL mean something an on-call human should care about? [3] Are secrets and PII stripped by an automated allowlist, not developer memory? [16] Do you emit one canonical/wide line per request? [20] Are errors and warnings exempt from sampling? [18] Are timestamps UTC and ISO 8601? [12] If you can tick those, your next 3am page is going to be a lot shorter.\nThe goal was never \u0026ldquo;log everything.\u0026rdquo; It\u0026rsquo;s to make sure that when something breaks — and it will — the answer is already sitting in a query you can write in fifteen seconds, half-awake, with the pager still buzzing.\nSources Logging Best Practices: 12 Tips for Developers and SREs — Parseable Logging Levels Explained — SigNoz Log Debug vs. Info vs. Warn vs. Error and Fatal — Edge Delta Log Levels: Different Types and How to Use Them — Last9 Log Levels Explained and How to Use Them — Better Stack Logging Best Practices: 12 Dos and Don\u0026rsquo;ts — Better Stack Logging Best Practices: The 13 You Should Know — DataSet Pattern: Distributed tracing — microservices.io Understanding and Implementing Correlation ID in Microservices — Anil Goyal Distributed Tracing Logs: How They Work \u0026amp; Best Practices — groundcover Structured Logging in Production: Best Practices for Scalable Systems — OpenObserve Structured Logging: Best Practices \u0026amp; JSON Examples — Uptrace OpenTelemetry Context Propagation: W3C TraceContext — Uptrace Traceparent: How OpenTelemetry Connects Your Microservices — Last9 Logging Cheat Sheet — OWASP Best Logging Practices for Safeguarding Sensitive Data — Better Stack How to Keep Sensitive Data Out of Your Logs: 9 Best Practices — Skyflow Log Sampling: Techniques, Challenges \u0026amp; Best Practices — groundcover How to Reduce Logging Costs with Log Sampling — Better Stack Fast and flexible observability with canonical log lines — Stripe How to optimize high-volume log data without compromising visibility — Datadog ","permalink":"https://cloudmato.com/posts/logging-best-practices-production-troubleshooting/","title":"Logging Best Practices for Production Troubleshooting"},{"content":"You open Windows 11, right-click the Start menu, and you see: Command Prompt, PowerShell, Windows Terminal. Three things. You just wanted to run a quick command. Now you\u0026rsquo;re questioning your life choices.\nThis confusion is real, and honestly, Microsoft did not make it easy. But there\u0026rsquo;s a genuinely good reason PowerShell exists — and once you understand it, the whole picture starts to make sense.\nCMD Was Never Really a Shell — It Was a Stopgap Let\u0026rsquo;s start at the beginning. cmd.exe — the Command Prompt — has been around since Windows NT launched in December 1987 [1]. Before that, COMMAND.COM handled things in MS-DOS. So yeah, Windows has had a command-line interface for nearly 40 years.\nBut here\u0026rsquo;s the thing people miss: CMD was not designed for power users or administrators. It was designed to keep old DOS batch files running and let you do basic stuff — copy a file, check an IP address, run a program. That\u0026rsquo;s it. Its roots were in a single-user, single-tasking operating system from the 1980s, and that baggage never went away.\nThe evolution of the Windows command-line tells this story well — CMD was always playing catch-up, never really designed for the complexity that modern Windows systems demand [2].\nSome actual CMD limitations that drove people crazy:\nMaximum command line string length: 8,191 characters. Hit that ceiling trying to pass long file paths and you\u0026rsquo;d get cryptic failures [3]. No native support for objects — everything is plain text, so you had to parse output with string gymnastics. Scripting capabilities in .bat files are genuinely awful — conditional logic, loops, error handling — all painful. Zero built-in access to Windows internals like the registry, services, WMI, or Active Directory. No tab completion worth mentioning. No syntax highlighting. No modern UX. When you\u0026rsquo;re managing one PC, these limitations are annoying. When you\u0026rsquo;re a sysadmin responsible for hundreds or thousands of Windows machines in an enterprise? CMD simply doesn\u0026rsquo;t even come close to having what it takes [4]. You\u0026rsquo;d need to script things with VBScript or write custom tools just to do basic bulk administration. It was a mess.\nOne Guy Got Tired of It — And Wrote a Manifesto This is where the story gets interesting.\nIn 1999, a developer named Jeffrey Snover joined Microsoft. He had a Unix background and knew what a real shell could do. He looked at Windows system administration and was, by his own admission, horrified. Unix had bash, grep, awk, sed — a whole ecosystem of composable tools for managing systems at scale. Windows had\u0026hellip; .bat files.\nSnover spent a couple of years thinking about this problem. Then, on August 8, 2002, he wrote what became known as the Monad Manifesto — a 17-page document laying out his vision for a new kind of Windows shell [5]. He called the project Monad.\nThe manifesto is surprisingly readable. Snover\u0026rsquo;s core argument was this: Unix shells are powerful because they compose simple tools through pipelines of text. But text is fragile — you have to parse it, split it, count columns, and pray the output format never changes. What if instead, you piped objects? Structured data with named properties, not raw strings?\nThat idea — objects in the pipeline, not text — is the single biggest thing that separates PowerShell from CMD (and honestly from bash too).\nMonad was renamed and PowerShell 1.0 was officially released in November 2006 [6]. It took four years from the manifesto to ship.\nWhat PowerShell Actually Does Differently Okay, enough history — let\u0026rsquo;s talk about why this matters practically.\nObjects, Not Text When you run Get-Process in PowerShell, you don\u0026rsquo;t get a string. You get a collection of System.Diagnostics.Process .NET objects — each one with properties like Name, Id, CPU, WorkingSet [7]. They have types. You can filter them, sort them, compare them, without ever touching string parsing.\nCompare:\n# PowerShell — reads like English, rock solid Get-Process | Where-Object WorkingSet -gt 100MB | Sort-Object CPU -Descending REM CMD — parsing text by character position, good luck tasklist /NH | sort /R /+65 The CMD version counts character positions in the output. Change Windows version, change the column width — it silently breaks. The PowerShell version? It\u0026rsquo;ll work fine regardless.\nIt\u0026rsquo;s Built on .NET — Which Means It Can Do Almost Anything PowerShell cmdlets are built on the .NET Framework (and later .NET Core). This means you can call .NET APIs directly from your shell. Want to interact with Active Directory, the Windows registry, Event Logs, COM objects, REST APIs, or WMI? PowerShell has native cmdlets for all of this [4].\nCMD literally cannot do any of those things without external tools.\nRemote Management at Scale Invoke-Command lets you run a PowerShell script on 500 remote Windows machines simultaneously. That\u0026rsquo;s not an exaggeration — it\u0026rsquo;s what enterprise sysadmins actually use it for. CMD has no equivalent. Zero.\nAnd Then Microsoft Created the Confusion Here\u0026rsquo;s where things go sideways.\nPowerShell 1.0 through 5.1 — called Windows PowerShell — was Windows-only, built on the old .NET Framework. Then in August 2016, Microsoft did something nobody expected: they open-sourced PowerShell and released it for Linux and macOS [8]. They rebuilt it on .NET Core and called the new thing PowerShell Core 6.0, eventually just PowerShell 7.\nSo now there are — and I\u0026rsquo;m not joking — two different PowerShells:\nFeature Windows PowerShell 5.1 PowerShell 7.x Executable powershell.exe pwsh.exe Built on .NET Framework .NET Core / .NET 5+ Platform Windows only Windows, Linux, macOS Status Maintenance only Actively developed Pre-installed Yes (Windows) No, separate install Windows PowerShell 5.1 is no longer getting new features — Microsoft only patches it for security bugs [9]. PowerShell 7 is where all the new stuff happens. But Windows still ships with 5.1 pre-installed, and PowerShell 7 is a separate download.\nSo if you open the \u0026ldquo;PowerShell\u0026rdquo; that comes with Windows — that\u0026rsquo;s the old one. And some modules that work in 5.1 don\u0026rsquo;t work in 7 yet. And vice versa. Honestly, this is where it gets tricky.\nAnd Then Windows Terminal Showed Up In May 2019 at Microsoft Build, they announced Windows Terminal — a brand new tabbed terminal application [10]. It went stable in May 2020.\nHere\u0026rsquo;s the thing though: Windows Terminal is not a shell. It\u0026rsquo;s just a host app — a pretty wrapper that can run CMD, PowerShell 5.1, PowerShell 7, WSL (Windows Subsystem for Linux), or SSH sessions — all in tabs, with GPU-accelerated text rendering, themes, custom fonts, and split panes.\nThink of it this way:\nCMD = a shell (the engine, the interpreter) PowerShell = a shell (a more powerful engine) Windows Terminal = the window that runs those shells inside it The problem is that when you open Windows Terminal on Windows 11, it defaults to PowerShell. So a lot of people think Windows Terminal is PowerShell. It\u0026rsquo;s not. It\u0026rsquo;s just wearing PowerShell as a default.\nThe mental model Microsoft expected people to figure out on their own:\nWindows Terminal (the window) ├── PowerShell 7 tab ← modern shell ├── Windows PowerShell 5.1 tab ← legacy shell ├── Command Prompt tab ← ancient shell └── Ubuntu (WSL) tab ← Linux shell on Windows So Why Not Just Fix CMD? Fair question. Why didn\u0026rsquo;t Microsoft just upgrade CMD instead of building something completely new?\nThe answer is backwards compatibility — the thing that haunts every Windows decision ever made. CMD is used by literally millions of batch scripts running in enterprises, hospitals, banks, government systems. Touch cmd.exe, and something critical breaks. The CMD engine is intentionally in maintenance mode because \u0026ldquo;every time a change is made, something critical breaks\u0026rdquo; [11].\nThere was also a strategic architectural reason: around 2002, Microsoft was moving from COM (Component Object Model) programming to the .NET Framework. Building a new shell on .NET made total sense. Retrofitting .NET onto a DOS-era interpreter would have been a nightmare.\nSo they built PowerShell as a parallel track — new users get it, old systems keep running CMD, eventually CMD fades out. In theory. In practice, CMD is still there 20 years later, confusing everyone.\nShould You Actually Learn PowerShell? If you\u0026rsquo;re a developer or sysadmin on Windows, the answer is yes, and specifically PowerShell 7 — the cross-platform one you install separately (pwsh.exe). It\u0026rsquo;s actively maintained, works on Linux and Mac, and is what Microsoft is investing in going forward [12].\nIf you\u0026rsquo;re just running quick one-off commands — finding a file, checking network config — CMD still works fine for that. Nobody\u0026rsquo;s stopping you.\nThe real trap is thinking these tools are interchangeable. They\u0026rsquo;re not. PowerShell is a proper scripting language with loops, functions, error handling, classes, and remote execution. CMD is a command runner with glorified batch files. Using CMD for automation in 2026 is like writing a web app in raw CGI — technically possible, practically painful.\nHere\u0026rsquo;s a rough guide:\nUse CMD when: you\u0026rsquo;re running legacy .bat scripts, doing a quick ipconfig or ping, or working in a locked-down enterprise environment Use Windows PowerShell 5.1 when: you need modules that aren\u0026rsquo;t yet on PS7, or you\u0026rsquo;re on a machine where installing PS7 isn\u0026rsquo;t an option Use PowerShell 7 when: everything else — automation, scripting, DevOps, cloud management, anything serious Use Windows Terminal when: you want all of the above in one nice tabbed window with themes The Real Answer to \u0026ldquo;Why Did Microsoft Do This?\u0026rdquo; Microsoft didn\u0026rsquo;t make it confusing on purpose. They made a genuinely good engineering decision (PowerShell is legitimately better than CMD for administration), but then layered on years of naming decisions, backwards compatibility constraints, an open-source pivot, two parallel product lines, and a new terminal host app — all without ever clearly explaining the mental model to end users.\nCMD stuck around because it had to. PowerShell was created because CMD couldn\u0026rsquo;t scale. Windows Terminal arrived because the old console host was embarrassingly ancient. And PowerShell 7 exists alongside 5.1 because cross-platform was a bigger architectural change than a version bump could handle.\nNone of this is irrational — it\u0026rsquo;s just decades of accumulated decisions that nobody bothered to map into a simple picture for regular people. Which is very, very on-brand for Microsoft.\nEnd\nSources Cmd.exe — Wikipedia Windows Command-Line: The Evolution of the Windows Command-Line — Microsoft DevBlogs Command prompt line string limitation — Microsoft Learn 2.1. Limitations of CMD.exe — O\u0026rsquo;Reilly Professional Windows PowerShell The Monad Manifesto — Microsoft Learn Monad Manifesto – the Origin of Windows PowerShell — PowerShell Team Blog PowerShell Object Pipelines vs. CMD Text Parsing — Windows News AI PowerShell is now open-source and cross-platform — .NET Blog Migrating from Windows PowerShell 5.1 to PowerShell 7 — Microsoft Learn Introducing Windows Terminal — Windows Command Line Blog CMD is in maintenance mode — The Register No future for Windows PowerShell — change to PowerShell Core — 4sysops ","permalink":"https://cloudmato.com/posts/why-powershell-when-cmd-exists/","title":"Why PowerShell Exists When CMD Was Already There"},{"content":"At some point in a large enough frontend codebase, the build itself becomes the bottleneck. Thousands of components, dozens of teams, one monolithic bundle — something has to give. Module Federation is one of the more interesting solutions people have reached for, and it\u0026rsquo;s worth understanding properly before you commit to it.\nWhat Is Module Federation? Module Federation is a feature introduced in Webpack 5 that lets JavaScript applications dynamically load code from other JavaScript applications at runtime — not at build time [1]. That single distinction is what makes it different from everything else.\nThink about how code sharing normally works. You want to share a React component between two apps, so you publish it to npm. The other app installs it, both apps bundle that code into their own output, and every time the component changes you bump the version, publish, the other app updates its dependency, and rebuilds. It works. At scale, it gets tedious fast.\nModule Federation flips this entirely. The app that owns the component exposes it as a remote module. Any other app — called the host — can consume that component directly at runtime over the network, without ever installing it as a dependency [7]. The code arrives when it\u0026rsquo;s needed, not baked in at build time.\nZack Jackson, a Webpack maintainer, invented Module Federation, and it shipped as a flagship feature of Webpack 5 in 2020 [1]. What started as a Webpack-specific feature has since grown into something much bigger — but we\u0026rsquo;ll get to that.\nHow It Actually Works Three core concepts you need to understand before anything else makes sense:\nHost — the application that consumes remote modules. This is your shell or container app. It knows about remotes and loads them when needed. Remote — the application that exposes modules for others to consume. It publishes a remoteEntry.js file that describes what it exposes and what it needs. Shared — dependencies that are shared across host and remotes to avoid loading the same library twice. React, ReactDOM, and any context-heavy library typically go here. Here\u0026rsquo;s what the runtime interaction looks like:\nThe host reaches out to each remote\u0026rsquo;s remoteEntry.js — a manifest of what that remote exposes and what shared dependencies it needs. Modules are loaded on demand, not all at once. Shared dependencies like React are loaded once and made available to all remotes through a global shareScope [1]. If Remote A and Remote B both declare react as shared, only one copy of React runs in the browser.\nOne thing that catches people off guard early: your host\u0026rsquo;s index.js must be async. You need a bootstrap file that dynamically imports the actual entry point, because the shared scope has to initialise before any module code runs [7]. Skip this and you\u0026rsquo;ll spend an afternoon staring at cryptic \u0026ldquo;module loaded before container initialised\u0026rdquo; errors.\nThe Advantages (And Why They Actually Matter) Independent Deployment This is the core value proposition, and it\u0026rsquo;s real. Each remote is its own independently deployable application. Team A ships the checkout remote, Team B ships the product catalog remote, and neither has to coordinate a release with the other [3].\nAt Walmart, this drove their micro-frontend journey with Module Federation — giving teams complete flexibility to develop and deploy independently while presenting users with a seamless single-page experience [3]. When you\u0026rsquo;re running 50+ engineers on a shared UI, waiting for a coordinated deploy across a dozen teams is a real, measurable cost.\nNo More Internal npm Publish Loops One of the most underappreciated advantages: you don\u0026rsquo;t need to publish npm packages for internal component sharing. The standard workflow for internal libraries goes: change the component → bump version → publish → consuming app updates its dependency → rebuilds. On a fast-moving project, this loop gets old fast.\nPayPal moved to Module Federation exactly for this reason — to publish and consume UI components without a centralised package repository, sharing React components with just a few lines of configuration sprinkled into each app [2]. When you update the remote, the host picks up the change on next load. No version bumps. No publish pipeline. No \u0026ldquo;which version is production running again?\u0026rdquo;\nTechnology Flexibility This is where it gets genuinely interesting. Remotes don\u0026rsquo;t have to use the same framework as the host. A Vue remote can live inside a React host. An Angular micro-frontend can be loaded by a vanilla JS shell [5]. You set up the interoperability layer, but Module Federation doesn\u0026rsquo;t enforce technology uniformity.\nThis matters most during migrations. If you\u0026rsquo;re slowly moving a legacy Angular app toward React (or vice versa), Module Federation lets both coexist in production simultaneously, rather than forcing a big-bang rewrite. Not something you\u0026rsquo;d want forever — but extremely practical as a transition strategy.\nRuntime Composition and Dynamic Loading The host doesn\u0026rsquo;t need to know about all its remotes at build time. You can load different remotes based on user roles, feature flags, A/B test cohorts — all without redeploying the host [7]. Zalando built their modular portal using exactly this pattern, loading different sections of the UI on demand as users navigate [10].\nCombine this with lazy loading and you get real improvements to initial load time. The host ships lean; remotes load only when a user actually navigates to that section. The web performance benefits are genuine — just not automatic, which brings us to the other side.\nThe Disadvantages You\u0026rsquo;ll Hit Eventually Most articles get vague here. I\u0026rsquo;ll try to be more useful.\nRuntime Failures Are Harder to Catch When you import an npm package and it breaks, it breaks at build time or immediately on app startup — visible, loud, caught by CI. With Module Federation, failures happen at runtime, when the host tries to fetch and evaluate a remote module [5]. If a remote is down, or a team deployed a breaking change, users see errors in production that your CI never caught — because each app passed its own tests in isolation.\nDebugging these failures is genuinely harder. You\u0026rsquo;re not dealing with a local dependency. You\u0026rsquo;re dealing with a network request, a version negotiation, and a module evaluation step that can each fail independently, often with unhelpful error messages. You need canary deployments, defensive fallbacks, and proper observability set up before you hit this in production — not after [14].\nDependency Version Conflicts Honestly, this is where it gets tricky. The shared configuration lets you declare which packages should be shared, but semantic versioning ranges don\u0026rsquo;t always resolve cleanly at runtime. Each remote is built by a separate Webpack process at a different point in time, so they can only rely on semver ranges to deduplicate — there\u0026rsquo;s no lockfile enforced across remotes [8].\nYou might build and test a remote against React 18.2.0, but at runtime the host is serving 18.3.0 (it matches ^18.0.0), and there\u0026rsquo;s a subtle API difference. Or worse — a prerelease version tag like 2.2.2-release.99 breaks singleton matching entirely, causing each remote to load its own separate instance of the library GitHub issue module-federation/core #4078. Multiple copies of React in the browser is not a fun debugging session.\nLibraries that depend on global state — React context, Angular\u0026rsquo;s dependency injector, any singleton store — are especially sensitive. Two instances of @angular/core or two React roots with mismatched context is not recoverable without a page reload. Nx has solid documentation on managing library versions in federated setups, and it\u0026rsquo;s worth reading carefully before you design your shared config [12].\nPerformance Is Not Automatic The intuition is that splitting code across remotes reduces bundle size. That\u0026rsquo;s partly true — but there\u0026rsquo;s a catch that catches most people off guard: exposing a module disables tree shaking for that module [11]. Webpack can\u0026rsquo;t know ahead of time which exports the remote consumer will use, so it bundles everything the remote exposes. An exposed module that\u0026rsquo;s only partially consumed ships dead code.\nThere\u0026rsquo;s also the latency of fetching remoteEntry.js files for each remote before the app becomes interactive. On a fast connection this is negligible. On slower networks, or with many remotes, it adds up visibly. Lazy loading — where remotes load on route change rather than app start — is the right default, but it requires deliberate configuration. It doesn\u0026rsquo;t happen automatically [9].\nTesting Gets Complicated Fast Unit tests for individual remotes are fine. Integration testing is where the complexity surfaces. To test how Remote A behaves inside the Host when Remote B is also loaded, you need all three running together. Contract testing — making sure a remote\u0026rsquo;s exported API doesn\u0026rsquo;t silently break its consumers — becomes a necessity, not a nice-to-have [6].\nMost teams underestimate this. Their remote tests pass, their host tests pass, and then something breaks in the combined runtime because nobody tested the combination. Building a proper end-to-end test environment for federated apps takes real investment in tooling and process.\nWho\u0026rsquo;s Actually Using This in Production? Module Federation isn\u0026rsquo;t a toy. Companies running it in production include PayPal, Walmart, Netflix, Shopify, Adidas, JPMorgan Chase, Cloudflare, Amazon, Expedia, Cisco, Alibaba, and Housing.com [13]. The pattern across all of them is consistent: large engineering organisations, multiple teams owning distinct UI domains, a hard requirement for independent deployment without fragmenting the user experience.\nThe feature-sliced design team\u0026rsquo;s 2025 analysis is fairly blunt about the scale threshold: micro-frontends are an organisational solution, not a technical one [9]. If your entire app is built by five people, a monolith is significantly more efficient. The calculus starts changing around 50+ engineers shipping to the same UI.\nModule Federation vs the Alternatives Approach Integration Point Independent Deploy Framework Freedom Complexity Module Federation Runtime ✅ Yes ✅ Yes High npm packages Build time ❌ No ✅ Yes Low Iframes Runtime ✅ Yes ✅ Yes Medium Monorepo (shared code) Build time ❌ No Limited Medium Server-side composition Server ✅ Yes ✅ Yes High The honest read: Module Federation is the most powerful option, and the most complex. If your real requirement is \u0026ldquo;share components between two apps owned by the same team,\u0026rdquo; npm packages or a well-structured monorepo will serve you better and sleep more soundly. Module Federation\u0026rsquo;s complexity pays off specifically when independent deployment across team boundaries is a hard requirement — not a preference [6].\nThe Module Federation 2.0 Era In April 2026, Module Federation 2.0 reached stable release, and it\u0026rsquo;s a meaningful step forward [4]. The 2.0 release decouples the runtime entirely from the build tool, standardising the implementation across bundlers. This means consistent module loading behaviour regardless of whether teams use Webpack, Rspack, Rollup, Rolldown, or Vite.\nThat Vite support is significant. Most greenfield projects in 2026 are on Vite, and Module Federation being a Webpack-only concept was a real adoption blocker. Framework coverage now includes Next.js, Modern.js, React, Vue, and React Native [4].\nKey additions in 2.0 that are worth knowing about:\nTypeScript type sharing across remotes — you can now share types without publishing a separate package. This alone solves a serious DX problem. Manifest-based dynamic host discovery — the host doesn\u0026rsquo;t need to hardcode remote URLs at build time. Dynamic resolution at runtime. First-class Node.js runtime support — remote modules can now be consumed by SSR layers, BFF services, and Node microservices, not just browsers. A unified runtime API that works consistently regardless of bundler, so switching a team from Webpack to Rspack doesn\u0026rsquo;t break federation [4]. If you\u0026rsquo;re evaluating Module Federation today, start with 2.0 and the official documentation at module-federation.io rather than the Webpack 5 docs — the configuration API has changed enough that older tutorials will send you in circles [11].\nShould Your Team Actually Use It? Module Federation solves an organisational problem, not a technical one. It exists because large companies have many teams that need to ship independently to the same UI surface. If that\u0026rsquo;s not your situation, the complexity it introduces isn\u0026rsquo;t worth it.\nThe checklist before committing:\nDo you have multiple teams that genuinely need independent deployment pipelines? Are your UI domains loosely coupled enough that one team can ship without testing another team\u0026rsquo;s work? Do you have the capacity to invest in contract testing, observability, and canary rollouts? Can your teams maintain disciplined shared-dependency configs across independently built apps? If the answer to most of those is yes, Module Federation is a serious option with real precedent at scale. If you\u0026rsquo;re mostly answering \u0026ldquo;not really\u0026rdquo; — stick with a monorepo and npm packages. You\u0026rsquo;ll ship faster and debug less.\nEnd\nSources Module Federation — webpack Decentralizing UI Development with Module Federation — PayPal Tech Blog Module Federation using Webpack 5: The Micro-frontend Journey — Walmart Global Tech Blog Module Federation 2.0 Reaches Stable Release with Wider Support outside of Webpack — InfoQ Solving micro-frontend challenges with Module Federation — LogRocket Blog Should Your Team Be Using Micro Frontends and Module Federation? — Bitovi What Is Webpack Module Federation and Why Does It Matter? — Syncfusion Blogs Pitfalls with Module Federation and Angular — ANGULARarchitects Micro-Frontends: Are They Still Worth It in 2025? — Feature-Sliced Design Building a Modular Portal with Webpack Module Federation — Zalando Engineering Blog Module Federation Official Documentation Manage Library Versions with Module Federation — Nx Module Federation for the Business — Valor Software Unlocking Team Efficiency with Module Federation: A Strategic Approach to Micro Frontends — NextSteps ","permalink":"https://cloudmato.com/posts/module-federation-ui-development/","title":"Module Federation in UI: The Good, Bad, and Ugly"},{"content":"Module Federation is one of those concepts everyone name-drops the moment \u0026ldquo;micro frontends\u0026rdquo; comes up in a meeting. Cool, share code at runtime, deploy teams independently, sounds great. Then someone on the team says \u0026ldquo;we\u0026rsquo;re on Vite, not webpack\u0026rdquo; and the whole conversation gets awkward. So what actually happens when you try to bring Module Federation into a Vite project? Some of it works beautifully. Some of it\u0026hellip; really doesn\u0026rsquo;t, at least not yet.\nOkay, so what is Module Federation again? Quick refresher because this matters for understanding the Vite story. Module Federation lets one JavaScript application load code from another application at runtime, not at build time. One app (the \u0026ldquo;remote\u0026rdquo;) exposes some modules — a component, a utility, a whole page — and bundles them into a small entry file, usually called remoteEntry.js. Another app (the \u0026ldquo;host\u0026rdquo;) imports that entry file dynamically and uses the exposed code as if it were a normal import.\nThis was originally a webpack 5 feature, baked deeply into webpack\u0026rsquo;s chunk-loading runtime. Webpack treats it as a first-class concept — its whole module graph, code-splitting, and async chunk loader were designed with this in mind.\nVite, on the other hand, was never built around this idea. Vite\u0026rsquo;s dev server serves native ES modules directly to the browser, and its production builds go through Rollup. Neither of those has any concept of \u0026ldquo;load a remote module graph at runtime and merge it with mine.\u0026rdquo; So Vite doesn\u0026rsquo;t have Module Federation built in — full stop. Everything you do here is bolted on by community (or community-turned-official) plugins.\nVite doesn\u0026rsquo;t have this built in — here\u0026rsquo;s who fills the gap There are two main players, and honestly, picking between them is the first real decision you\u0026rsquo;ll make.\nThe older and more battle-tested one is @originjs/vite-plugin-federation [1]. It ships its own runtime built around a virtual module (virtual:__federation__), and it was explicitly designed to feel familiar to people coming from webpack\u0026rsquo;s Module Federation — same mental model of exposes, remotes, and shared.\nThe newer one is @module-federation/vite [2], maintained by the same team behind Module Federation 2.0. Instead of inventing its own runtime, it wires Vite directly into @module-federation/runtime, the same runtime that powers the webpack and Rspack implementations. That matters because Module Federation 2.0 was rebuilt specifically to decouple the runtime from any particular bundler — the goal being that a remote built with Rspack and a host built with Vite can talk to each other using the same protocol [3].\nThere\u0026rsquo;s also a third name floating around now: a standalone vite-plugin-federation package that hit a 1.0 release positioning itself as the \u0026ldquo;production era\u0026rdquo; option, with a manifest-first approach (mf-manifest.json, mf-stats.json, mf-debug.json) and built-in governance features like circuit breakers and SRI verification [4]. Worth knowing it exists, but I\u0026rsquo;d treat anything this new with a healthy dose of \u0026ldquo;let\u0026rsquo;s see how it holds up in the wild.\u0026rdquo;\nHere\u0026rsquo;s how I\u0026rsquo;d frame the choice:\n@originjs/vite-plugin-federation @module-federation/vite Runtime Its own, webpack-MF-inspired @module-federation/runtime (shared with webpack/Rspack) Maturity Older, widely used, lots of GitHub issues already triaged Newer, fewer battle scars, actively developed Cross-bundler interop Mostly Vite-to-Vite Designed to interoperate with Rspack/webpack remotes [3] TypeScript type sharing Limited Built around MF 2.0 features like dynamic type hints [3] Best fit Existing OriginJS-style setups, simple host/remote pairs New projects, especially ones already touching the wider Module Federation ecosystem If you\u0026rsquo;re starting fresh today and there\u0026rsquo;s any chance you\u0026rsquo;ll mix bundlers down the line, lean toward @module-federation/vite. If you just want the simplest possible \u0026ldquo;host imports a button from a remote\u0026rdquo; setup and don\u0026rsquo;t care about the bigger ecosystem, @originjs/vite-plugin-federation still works fine and has more Stack Overflow answers written about it.\nSetting up a basic host + remote (the part that actually works) This is the part that genuinely just works, and honestly it\u0026rsquo;s kind of magical the first time you see it. Here\u0026rsquo;s a minimal remote app exposing a component:\n// remote/vite.config.ts import { defineConfig } from \u0026#39;vite\u0026#39; import react from \u0026#39;@vitejs/plugin-react\u0026#39; import federation from \u0026#39;@originjs/vite-plugin-federation\u0026#39; export default defineConfig({ plugins: [ react(), federation({ name: \u0026#39;remote_app\u0026#39;, filename: \u0026#39;remoteEntry.js\u0026#39;, exposes: { \u0026#39;./Button\u0026#39;: \u0026#39;./src/components/Button.tsx\u0026#39;, }, shared: [\u0026#39;react\u0026#39;, \u0026#39;react-dom\u0026#39;], }), ], build: { target: \u0026#39;esnext\u0026#39;, minify: false, cssCodeSplit: false, }, }) And the host that consumes it:\n// host/vite.config.ts import { defineConfig } from \u0026#39;vite\u0026#39; import react from \u0026#39;@vitejs/plugin-react\u0026#39; import federation from \u0026#39;@originjs/vite-plugin-federation\u0026#39; export default defineConfig({ plugins: [ react(), federation({ name: \u0026#39;host_app\u0026#39;, remotes: { remote_app: \u0026#39;http://localhost:5001/assets/remoteEntry.js\u0026#39;, }, shared: [\u0026#39;react\u0026#39;, \u0026#39;react-dom\u0026#39;], }), ], build: { target: \u0026#39;esnext\u0026#39; }, }) Then in the host, you consume it like any lazy component:\nconst RemoteButton = React.lazy(() =\u0026gt; import(\u0026#39;remote_app/Button\u0026#39;) ) You build the remote first (vite build), serve the dist folder somewhere, and then run the host. That\u0026rsquo;s the whole magic trick — the host fetches remoteEntry.js over the network, figures out what\u0026rsquo;s exposed, and pulls in the code on demand. No iframe, no separate React root mounted weirdly — it\u0026rsquo;s just\u0026hellip; there, as a component.\nWhat you CAN do with Module Federation in Vite Once it\u0026rsquo;s wired up, a surprising amount of the webpack-era promise holds true. Here\u0026rsquo;s what genuinely works in practice:\nExpose and consume components, hooks, and utilities at runtime across separately built and deployed apps — the core use case, and it works. Share singleton dependencies like React, Vue, or a design-system package so the host and remotes don\u0026rsquo;t each ship their own copy [7]. The shared config with singleton: true resolves to one shared instance. Lazy-load remotes only when needed, which is great for things like an admin panel or a checkout flow that most users never touch. Mix frameworks — a Vue host can technically load a React remote (or vice versa) by mounting it into a wrapper component, since federation just hands you a module, not a framework contract. Generate manifests (mf-manifest.json and similar) that describe what each remote exposes, which is genuinely useful for CI pipelines that want to verify compatibility before deploying [4]. Use TypeScript type hints across remotes, a Module Federation 2.0 feature that generates and downloads .d.ts files for remote modules so your editor isn\u0026rsquo;t just guessing at any [3]. Runtime plugins — hooks into the loading lifecycle (before request, after resolve, on error) that let you add logging, retries, or fallback URLs without touching application code [3]. Independent deploys — once both apps agree on the federation contract, you really can ship the remote without redeploying the host, which is the whole point of this exercise. That last one is the actual payoff. If your org has multiple teams stepping on each other in one monorepo, being able to deploy \u0026ldquo;checkout\u0026rdquo; on Tuesday and \u0026ldquo;profile\u0026rdquo; on Thursday without coordinating a release is a real win — when it works.\nWhere it falls apart: dev mode is the elephant in the room Here\u0026rsquo;s where it gets tricky, and honestly, this is the thing that trips up almost everyone the first time.\nOnly the host side gets a proper Vite dev server experience. The remote has to be built first — its remoteEntry.js and exposed chunks need to exist as static files before the host can import them [5]. There\u0026rsquo;s no equivalent of \u0026ldquo;both apps running vite dev with full HMR talking to each other live.\u0026rdquo;\nWhat this means day to day:\nYou change something in the remote\u0026rsquo;s exposed component. Vite\u0026rsquo;s dev server (running just for the remote, for your own convenience) doesn\u0026rsquo;t help the host at all. You have to run vite build on the remote again. The host then needs a hard refresh to pick up the new remoteEntry.js. That\u0026rsquo;s not \u0026ldquo;fast feedback loop,\u0026rdquo; that\u0026rsquo;s \u0026ldquo;context switch every time you touch shared code.\u0026rdquo; Some teams work around this by running the remote\u0026rsquo;s vite build --watch in one terminal and just living with the refresh — it\u0026rsquo;s not elegant, but it\u0026rsquo;s workable for the host team. For the remote team actively developing their own UI, they typically just run their app standalone (without federation) using normal Vite dev, and only test the federated integration occasionally.\nA few other things that don\u0026rsquo;t work, or only half-work:\nbuild.rollupOptions.output.manualChunks is effectively off-limits. The federation plugin manages the chunk graph itself, and custom chunk grouping can break the bootstrap order the federation runtime relies on [2]. Mixing Vite/Rollup remotes with webpack hosts (or vice versa) is fragile. There\u0026rsquo;s no guarantee Rollup and webpack will produce the same chunk shape for CommonJS dependencies, which can quietly break shared resolution [1]. baseUrl / custom base paths have had real bugs. If your remote is served from a subpath (common in enterprise setups behind a reverse proxy), there have been issues where the federation plugin doesn\u0026rsquo;t resolve remoteEntry.js correctly under Vite 5+ [6]. Non-ESM output formats are second-class. The plugin docs are upfront that ESM is the well-tested path; UMD/CJS-style remotes \u0026ldquo;lack complete test cases\u0026rdquo; [1]. The CSS mess nobody warns you about This one bit me hard the first time, and it seems to bite basically everyone eventually. In dev mode, your remote\u0026rsquo;s styles load fine because Vite injects them via its dev server. In production, Vite does CSS code-splitting by default — each chunk gets its own CSS file, loaded via a \u0026lt;link\u0026gt; tag that the page needs to know about.\nThe problem? When a host dynamically imports a remote\u0026rsquo;s component, nothing tells the host\u0026rsquo;s HTML to load the remote\u0026rsquo;s CSS file. So you get a perfectly functional, completely unstyled component. One developer described it exactly right: it \u0026ldquo;looked like an unstyled wireframe\u0026rdquo; in production despite working fine locally [10].\nThe fixes people land on:\nSet build.cssCodeSplit: false on the remote so all its CSS bundles into a single file you can reliably reference. Use a plugin like vite-plugin-css-injected-by-js to inline the CSS directly into the JS bundle — ugly, but it guarantees the styles travel with the component [10]. Some teams go further and use CSS Modules or :host/Shadow DOM-style scoping to make sure a remote\u0026rsquo;s styles can\u0026rsquo;t accidentally leak into (or get clobbered by) the host\u0026rsquo;s global styles — a separate but related headache, since Vite\u0026rsquo;s own CSS Modules composes handling has had its own duplication quirks. None of this is exotic, but it\u0026rsquo;s exactly the kind of thing that works perfectly on your machine and falls over the moment QA opens the deployed build.\nShared dependencies and the singleton trap The shared config is where Module Federation earns its keep — or where it quietly ruins your day. The idea is simple: instead of every remote shipping its own copy of React, you mark it shared, and ideally singleton: true, so there\u0026rsquo;s exactly one React instance for the whole federated app [7].\nIn practice, version mismatches are the single most common production bug in federated setups [8]. If the host expects React 18.2 and a remote was built against React 18.3, you can end up with two React instances anyway — and React\u0026rsquo;s hooks rules absolutely do not forgive that. The classic symptom is \u0026ldquo;Invalid hook call\u0026rdquo; errors that make zero sense until you realize there are two Reacts in memory.\nA few things worth knowing here:\nsingleton: true tells the runtime to pick one version and force everyone to use it — even if it\u0026rsquo;s not technically compatible with everyone\u0026rsquo;s requiredVersion [7]. It\u0026rsquo;s a \u0026ldquo;best effort, don\u0026rsquo;t crash\u0026rdquo; setting, not a guarantee of correctness. strictVersion: true flips that around — instead of silently picking a version, it throws if there\u0026rsquo;s an incompatibility. Better for catching problems in CI than discovering them in production. There have been real bugs around version strings with build metadata or pre-release suffixes (like 18.2.0-release.99) not resolving correctly against semver ranges, which can silently defeat the whole shared-singleton mechanism [9]. Multiple remotes pulling in slightly different versions of a shared lib has caused the opposite problem too — dependencies getting fetched and bundled multiple times instead of being deduplicated, especially with react-router-dom in more complex routing setups [15]. My honest take: the shared config is necessary but not sufficient. You still need a real dependency-version discipline across teams — ideally a shared package.json or at least a documented \u0026ldquo;these are the pinned versions everyone targets\u0026rdquo; doc. Module Federation reduces duplication; it doesn\u0026rsquo;t enforce alignment.\nShould you even bother? Alternatives worth knowing This is the question I\u0026rsquo;d ask before writing a single line of federation config: do you actually need this, or do you need independent deploys and a simpler tool would do?\nA few alternatives that come up a lot in 2026 conversations:\nImport Maps. A real web standard (Chrome since 2021) that lets the browser itself resolve module specifiers to versioned URLs via a JSON map — no bundler-specific runtime required [11]. If your \u0026ldquo;micro frontends\u0026rdquo; are really just a handful of independently versioned ES modules, import maps can get you most of the way there with far less tooling. Native Federation. Built by the Angular Architects team specifically for Vite/esbuild-style toolchains, it implements the Module Federation concept using import maps and native ESM rather than a custom runtime [12]. Framework-agnostic, and notably lighter weight than the webpack-derived approach. Module Federation 2.0 via Rspack. If your team is open to switching bundlers (not just adding a plugin), Rspack\u0026rsquo;s native Module Federation support is more mature than anything in the Vite ecosystem right now, and it speaks the same MF 2.0 protocol that @module-federation/vite uses — meaning a gradual Vite-to-Rspack migration for specific remotes is realistic [13][3]. A monorepo with shared packages and no runtime federation at all. Sometimes the actual requirement is \u0026ldquo;stop duplicating this Button component,\u0026rdquo; and a published internal npm package solves that without any of the runtime complexity. Boring, but boring is a feature. I\u0026rsquo;d genuinely consider import maps or Native Federation first if your main goal is \u0026ldquo;independently deployable\u0026rdquo; rather than \u0026ldquo;must work exactly like our old webpack setup.\u0026rdquo; The Vite federation plugins are solving a harder problem than they need to in a lot of cases, purely because teams want API parity with webpack.\nMy honest checklist before you commit to this If you\u0026rsquo;re about to start this, here\u0026rsquo;s the stuff I\u0026rsquo;d nail down before writing the first federation() config:\nQuestion Why it matters Do remotes need to be deployed independently of the host\u0026rsquo;s release cycle? If not, you might not need federation at all — a monorepo package may be simpler Can your team tolerate \u0026ldquo;rebuild remote, refresh host\u0026rdquo; during development? This is the dev-mode reality with both major plugins right now [5] Are you on target: 'esnext' and avoiding manualChunks? Both plugins require/expect this; fighting it causes obscure build failures [2] Do you have a plan for shared dependency versions across teams? singleton: true helps but doesn\u0026rsquo;t replace version discipline [8][9] Will remotes be served from a subpath/CDN with a non-root base? Known source of remoteEntry.js resolution bugs [6] Have you tested production builds, not just dev, for CSS? Styles that work in dev silently vanish in prod for many people [10] Could Native Federation or import maps cover your actual requirement? Sometimes the simpler standard does 80% of the job for 20% of the complexity [11][12] The honest summary of where things stand: Module Federation does work with Vite, and for the core \u0026ldquo;load a remote component at runtime, share a singleton dependency\u0026rdquo; use case, it works well. The rough edges show up the moment you push toward production — CSS, dev-mode parity, version drift, and non-root deployments. None of these are dealbreakers, but every single one of them will eat a debugging session if you don\u0026rsquo;t know they\u0026rsquo;re coming. Module Federation 2.0\u0026rsquo;s push toward a bundler-agnostic runtime [3] is the most promising sign that the Vite story will keep improving — but \u0026ldquo;improving\u0026rdquo; and \u0026ldquo;solved\u0026rdquo; are still two different words.\nSources originjs/vite-plugin-federation on GitHub @module-federation/vite on npm Module Federation 2.0 Reaches Stable Release with Wider Support outside of Webpack - InfoQ vite-plugin-federation 1.0: Bringing Module Federation Into the Production Era for Vite - DEV Community Support dev server remote entry file · Issue #525 · originjs/vite-plugin-federation Module Federation + base url · Issue #580 · originjs/vite-plugin-federation Shared configuration - Module Federation docs Getting Out of Version-Mismatch-Hell with Module Federation - ANGULARarchitects Module Federation Fails to Share Singleton Dependencies with Version Postfixes · Issue #4078 · module-federation/core How I Finally Got My Vite + Module Federation Styles to Load in Production You Might Not Need Module Federation: Orchestrate your Microfrontends at Runtime with Import Maps - Mercedes-Benz.io Announcing Native Federation 1.0 - ANGULARarchitects Module Federation 2.0: webpack vs Rspack vs Vite 2026 - PkgPulse Guides module-federation/vite on GitHub Bug Report: Multiple Instances of React and React-Router-DOM in Host and Remote · Issue #650 · originjs/vite-plugin-federation ","permalink":"https://cloudmato.com/posts/module-federation-vite-guide/","title":"Module Federation with Vite: What Works, What Doesn't"},{"content":"I type ls, cd, and grep probably a few hundred times a day. Never once, in 8+ years of doing this, did I stop to wonder where these tiny two-and-three letter words actually came from. Turns out, almost every single one of them has a story — some guy at Bell Labs in the early 1970s, working on a machine slower than your smartwatch, solving a problem he had that exact day. Let\u0026rsquo;s go through ten of the most-used Unix commands and dig into why they exist.\nls — the command that\u0026rsquo;s older than Unix itself Here\u0026rsquo;s something that surprised me: ls is technically older than Unix. Its ancestor was a command called listf on MIT\u0026rsquo;s Compatible Time Sharing System (CTSS), which was already running by July 1961 [1]. When CTSS evolved into Multics, listf got renamed to list, which could be abbreviated to — you guessed it — ls [1].\nWhen Ken Thompson and Dennis Ritchie built the first version of Unix at Bell Labs, they borrowed a bunch of ideas from Multics, and ls came along for the ride [2]. By the time the first Unix Programmer\u0026rsquo;s Manual was published on November 3, 1971, ls was already documented with several of the same options we still use today [1].\nSo ls is short for \u0026ldquo;list,\u0026rdquo; not for anything cleverer. No hidden acronym, no inside joke. It\u0026rsquo;s just one of the oldest surviving commands in computing, quietly doing the same job for over 60 years.\ncd and pwd — knowing where you are and how to get there Navigating a filesystem feels so basic that it\u0026rsquo;s easy to forget someone had to invent it. cd (\u0026ldquo;change directory\u0026rdquo;) was baked into the shell from very early on — Thompson\u0026rsquo;s original shell already had a chdir-style command, since multi-directory filesystems were one of Unix\u0026rsquo;s actual selling points over flatter systems of the era.\npwd (\u0026ldquo;print working directory\u0026rdquo;) showed up a bit later, with an initial release around June 1974 [3]. Why did it need to exist separately from cd? Because once you start cd-ing around a deep tree of directories, your brain loses track fast — pwd just answers the question \u0026ldquo;okay, but where am I right now?\u0026rdquo;\nOne little detail I genuinely didn\u0026rsquo;t know until researching this: cd - toggles you back to your previous directory by swapping the PWD and OLDPWD environment variables [3]. It\u0026rsquo;s been there for decades and I only started using it a couple of years ago. If you jump between two directories a lot — say, a project folder and its build output — cd - will save you an absurd number of keystrokes over a career.\ncat — concatenate, not just \u0026ldquo;dump this file\u0026rdquo; cat is one of those commands everyone uses for the \u0026ldquo;wrong\u0026rdquo; reason. It shipped on November 3, 1971, written by Thompson and Ritchie as part of the original Unix toolset [4]. The name is short for concatenate, itself derived from the Latin catena, meaning \u0026ldquo;chain\u0026rdquo; [4].\nThe actual original purpose: take multiple files, chain them together, and write the result to standard output. cat file1.txt file2.txt \u0026gt; combined.txt is the command doing exactly what its name says. But here\u0026rsquo;s the thing — cat is used far more often to just dump a single file to the screen than to concatenate anything [4]. That\u0026rsquo;s not what it was built for, it\u0026rsquo;s just the cheapest possible \u0026ldquo;show me what\u0026rsquo;s in this file\u0026rdquo; command, and it stuck.\nThis is also where the famous \u0026ldquo;useless use of cat\u0026rdquo; debate comes from — people piping cat file | grep something when grep something file would do. Is it \u0026ldquo;wrong\u0026rdquo;? Not really. Is it doing extra work for no reason? Also yes. I still catch myself doing it out of habit.\ncp, mv, and rm — the basic file-shuffling trio By Unix\u0026rsquo;s second edition manual in 1972, the core file management commands were already all there: cp, mv, rm, alongside cat, chmod, find, ls, and others [5]. mv was written by Thompson and Ritching for renaming and relocating files, and rm — short for \u0026ldquo;remove\u0026rdquo; — for deleting them [6][7].\nWhy did these need to be separate commands instead of, say, one \u0026ldquo;file manager\u0026rdquo; program? Because that\u0026rsquo;s the whole Unix philosophy baked in from day one: small programs, each doing one job, that you combine yourself [8]. Copying a file is a different operation from deleting one, so they\u0026rsquo;re different tools.\nThe thing about rm that\u0026rsquo;s worth sitting with: there\u0026rsquo;s no trash can, no \u0026ldquo;are you sure?\u0026rdquo;, no undo. rm -rf does exactly what it says, immediately, forever. I\u0026rsquo;ve watched people nuke the wrong directory by being one folder off, and there\u0026rsquo;s no recovering from it short of backups. That\u0026rsquo;s not a flaw exactly — it\u0026rsquo;s the same design philosophy that makes Unix tools fast and scriptable. You\u0026rsquo;re just expected to know what you\u0026rsquo;re doing.\ngrep — built overnight because someone asked nicely This one is my favorite story in the whole list. It\u0026rsquo;s 1973, and Doug McIlroy goes to Ken Thompson with a request: it would be really useful if you could search through files for a pattern without opening an editor [9]. Thompson\u0026rsquo;s reply, according to Brian Kernighan\u0026rsquo;s retelling: \u0026ldquo;Let me think about it overnight.\u0026rdquo; The next morning, he handed McIlroy a working program [9][10].\nHere\u0026rsquo;s the clever part — Thompson didn\u0026rsquo;t write a regex engine from scratch. He\u0026rsquo;d already built one for his text editor ed, where you could run a command like g/re/p — global, regular expression, print — to find and print matching lines [10]. He just pulled that exact logic out of the editor and turned it into a standalone program. The name grep is literally those three characters from the ed command [10].\ngrep first appeared in Unix v4 around November 1973, in a much more limited form than the version you use today [10]. But the core idea — \u0026ldquo;search text for a pattern, fast, without an editor\u0026rdquo; — has barely changed in 50 years. Every time you run grep -r \u0026quot;TODO\u0026quot; . across a codebase, you\u0026rsquo;re using a regex engine whose lineage traces back to a 1960s line editor.\nfind — searching the filesystem before \u0026ldquo;search\u0026rdquo; was a feature find is one of those commands people either love or quietly avoid because the syntax looks like ancient runes. But it\u0026rsquo;s been around since almost the very beginning — by the time Unix\u0026rsquo;s second edition manual was published in 1972, find was already sitting alongside ls, cp, and chmod as a standard tool [5].\nWhy did it need to exist when ls already lists files? Because ls only shows you one directory. As soon as you have a tree of directories — which Unix had from day one, unlike a lot of earlier systems — you need a way to ask \u0026ldquo;is there a file matching X anywhere under here?\u0026rdquo; find walks the entire tree recursively, testing every file against whatever conditions you give it (name, size, modification time, permissions, you name it).\nThe part that still impresses me is how composable it is. find . -name \u0026quot;*.log\u0026quot; -mtime +30 -delete is doing four jobs in one line: walk the tree, filter by name, filter by age, then act on the result. That\u0026rsquo;s the Unix philosophy again — find doesn\u0026rsquo;t need to know how to delete or compress or grep, it just needs to hand matching files off to whatever does.\nchmod — three letters, fifty years of the same permission model chmod — change mode — also dates back to November 3, 1971, as part of the original Unix release [11]. \u0026ldquo;Mode\u0026rdquo; here means the permission bits and special flags attached to a file [11].\nWhat\u0026rsquo;s wild is how little this has changed. The same read/write/execute triad for owner, group, and \u0026ldquo;everyone else\u0026rdquo; that Thompson and Ritchie designed for a single shared lab computer in 1971 is the exact same model securing your servers, your laptop, and probably your phone right now [11]. Whether you write chmod 755 script.sh or chmod u+x script.sh, you\u0026rsquo;re using a permission scheme designed for a room full of researchers sharing one PDP-11.\nWhy did it need to exist at all? Because Unix was, from the start, a multi-user system — multiple people sharing one machine, one filesystem [2]. The moment you have more than one user, you need a way to say \u0026ldquo;this file is mine, you can look but not touch.\u0026rdquo; chmod is the answer to that, and honestly, the fact that it\u0026rsquo;s barely changed in five decades says something about how well they got it right the first time.\nps — fifty years of answering \u0026ldquo;what is this machine doing?\u0026rdquo; ps, short for process status, turned 50 in January 2023 [12], which means it dates back to around 1973 — right in that same incredible stretch of Bell Labs history as grep and sed. Its job has stayed remarkably consistent: list the processes currently running, with their PIDs and other details [13].\nWhy was this needed on a system from the early \u0026rsquo;70s? Because Unix was a time-sharing system — lots of users, lots of programs running concurrently on hardware with a tiny fraction of the resources your phone has. If your terminal felt sluggish, you needed a way to ask \u0026ldquo;okay, what\u0026rsquo;s actually eating the CPU right now, and whose process is it?\u0026rdquo; ps was that diagnostic tool.\nHere\u0026rsquo;s a fun bit of trivia that explains why ps syntax feels so inconsistent compared to other commands: three different Unix lineages each invented their own flag conventions, and modern ps (the procps-ng package on most Linux distros) tries to support all of them at once [12][13]. That\u0026rsquo;s why ps aux (no dash, BSD-style) and ps -ef (System V style) both work and do almost the same thing — it\u0026rsquo;s not a design choice, it\u0026rsquo;s archaeology.\nawk — a programming language named after its three authors If you\u0026rsquo;ve ever used awk '{print $1}' to grab the first column of some output and wondered what on earth \u0026ldquo;awk\u0026rdquo; even means — it\u0026rsquo;s not an acronym for anything clever. It\u0026rsquo;s literally the last initials of the three people who wrote it in 1977 at Bell Labs: Aho, Weinberger, and Kernighan [14][15]. I genuinely did not know that until researching this, and now I can\u0026rsquo;t un-know it.\nawk was designed for a very specific gap: grep could find lines, sed could edit streams, but neither was great at \u0026ldquo;go through this file, treat each line as a record split into fields, and do something based on those fields.\u0026rdquo; Think log files, CSVs, /etc/passwd — anything organized into rows and columns. awk fills that gap with what is, technically, a full Turing-complete programming language, even though most people only ever use it for one-liners [14].\nIt didn\u0026rsquo;t stop in 1977 either. A much more powerful version arrived in 1985 with user-defined functions and computed regular expressions, which became widely available in System V Release 3.1 around 1987, then got further refinements in SVR4 in 1989 [14]. The GNU version, gawk, is what most Linux users are actually running today without realizing it.\nsed — the stream editor that grew out of a line editor Last one: sed, the stream editor, developed between 1973 and 1974 by Lee E. McMahon at Bell Labs [16]. If you\u0026rsquo;ve ever run sed 's/foo/bar/g' file.txt to do a quick find-and-replace across a file, you\u0026rsquo;ve used something that\u0026rsquo;s almost exactly as old as grep.\nsed is basically what happens when you take the editing commands from ed (and its predecessor, an even older editor called qed from the mid-1960s) and strip out the \u0026ldquo;interactive\u0026rdquo; part [16]. With ed, you sit at a terminal and type commands one at a time to edit a file. With sed, you write those same kinds of commands into a script, point it at a stream of text — a file, or more often the output of another program piped in — and it applies every edit automatically, with no human watching.\nWhy did this matter in 1974? Because not every computing task happens with a person sitting at a terminal. Batch processing — running a job unattended, often on a schedule, sometimes against output too large to scroll through by hand — was a huge part of how computers were used. sed let you describe an edit once and apply it to a million lines without touching a keyboard again. That\u0026rsquo;s still exactly why it\u0026rsquo;s useful in shell scripts and CI pipelines today.\nWhy all ten of these still feel \u0026ldquo;right\u0026rdquo; after 50 years Here\u0026rsquo;s the quick-reference version, if you want the whole table at a glance:\nCommand(s) First appeared Credited to Why it exists ls ~1971 (via Multics list) Multics team / Bell Labs [1][2] List what\u0026rsquo;s in a directory cd / pwd ~1971 / 1974 Bell Labs shell [3] Navigate and locate yourself in the tree cat Nov 1971 Thompson \u0026amp; Ritchie [4] Concatenate files / dump to output cp, mv, rm 1971-72 Bell Labs [5][6][7] Copy, rename, delete — basic file admin grep 1973 Ken Thompson, for Doug McIlroy [9][10] Search text without an editor find by 1972 Bell Labs [5] Locate files across a directory tree chmod Nov 1971 Thompson \u0026amp; Ritchie [11] Control read/write/execute access ps ~1973 Bell Labs [12][13] See what\u0026rsquo;s running on a shared machine awk 1977 Aho, Weinberger, Kernighan [14][15] Field-based text processing language sed 1973-74 Lee E. McMahon [16] Apply ed-style edits to a stream, unattended What gets me about this list is that almost every command exists because one specific person hit one specific wall on one specific day — McIlroy wanted to search files, so Thompson built grep overnight. Researchers were sharing a machine, so chmod had to exist. Batch jobs needed automated edits, so McMahon built sed. None of this was designed top-down as a \u0026ldquo;platform.\u0026rdquo; It accumulated, tool by tool, each one solving a real problem for the people in that building.\nAnd then Doug McIlroy\u0026rsquo;s idea from 1964 — that programs should connect \u0026ldquo;like a garden hose,\u0026rdquo; with one program\u0026rsquo;s output flowing straight into another\u0026rsquo;s input — got implemented by Thompson in 1973 as the | operator [17][18]. Suddenly all these small, separately-invented tools could be wired together in combinations nobody had specifically planned for. find . -name \u0026quot;*.log\u0026quot; | xargs grep ERROR | awk '{print $1}' | sort | uniq -c — every single piece of that pipeline was written by a different person, in a different year, for a different reason, and it still just works.\nFifty-plus years later, we\u0026rsquo;re all still using the same names, the same flags, mostly the same syntax. Maybe that\u0026rsquo;s the real story here — not that these commands are old, but that almost nobody felt the need to replace them.\nSources The Evolution of \u0026rsquo;ls\u0026rsquo;: From Early Unix to Modern Linux Unix and Multics Pwd - Wikipedia Cat (Unix) - Wikipedia Technology history: Where Unix came from - All Things Open Mv (Unix) - Wikipedia Rm (Unix) - Wikipedia Basics of the Unix Philosophy Brian Kernighan Remembers the Origins of \u0026lsquo;grep\u0026rsquo; - The New Stack Grep - Wikipedia Chmod - Wikipedia The history of the ps command ps – report process status - Unix Tutorial History - The GNU Awk User\u0026rsquo;s Guide AWK - Wikipedia Sed - Wikipedia Pipe: How the System Call That Ties Unix Together Came About - The New Stack features:pipes - Unix Heritage Wiki ","permalink":"https://cloudmato.com/posts/top-10-unix-commands-history/","title":"The History Behind 10 Unix Commands You Use Every Day"},{"content":"Every couple of weeks some AI lab drops a new model and immediately claims it\u0026rsquo;s the smartest thing on the planet. Then another lab does the same thing a week later. If you\u0026rsquo;ve ever tried to figure out which one is actually better, you\u0026rsquo;ve probably stared at a wall of charts with names like MMLU, GPQA, and SWE-bench and felt your eyes glaze over. I went down this rabbit hole recently, and here\u0026rsquo;s the short version: there\u0026rsquo;s no single scoreboard. There are at least four completely different ways people measure \u0026ldquo;better,\u0026rdquo; and once you know what each one is actually doing, the whole AI leaderboard circus starts to make a lot more sense.\nWhy \u0026ldquo;Which LLM Is Best?\u0026rdquo; Doesn\u0026rsquo;t Have One Answer Here\u0026rsquo;s the thing nobody tells you upfront: \u0026ldquo;best\u0026rdquo; depends entirely on what you\u0026rsquo;re using it for.\nA model that\u0026rsquo;s brilliant at writing poetry might be mediocre at fixing a Python bug. A model that crushes math competitions might give you a clunky, over-formatted email. And a model that tops every chart might cost 10x more per request and respond noticeably slower than one that\u0026rsquo;s \u0026ldquo;only\u0026rdquo; a few points behind.\nSo when people ask \u0026ldquo;is GPT better than Claude\u0026rdquo; or \u0026ldquo;is Gemini better than Llama,\u0026rdquo; the honest answer is: better at what, measured how, and compared on what budget? That\u0026rsquo;s not a cop-out — it\u0026rsquo;s basically the entire reason the AI benchmarking industry exists. Roughly speaking, the ways people measure model quality fall into four buckets:\nStandardized tests — give the model a fixed set of questions with known right answers, like a school exam. Human preference arenas — show real people two anonymous responses and let them vote on which one is better. LLM-as-a-judge — use one AI model to grade another model\u0026rsquo;s open-ended answers. Real-world task benchmarks — drop the model into something close to an actual job (fix this bug, complete this multi-step task) and see if it gets there. Let\u0026rsquo;s go through each one, because they each have a very different idea of what \u0026ldquo;smart\u0026rdquo; even means.\nMethod 1: The Standardized Test Approach This is the oldest and most familiar style — give the model a giant pile of questions, check the answers against a key, and report a percentage. It\u0026rsquo;s basically the SAT for AI.\nKnowledge and Reasoning Tests The granddaddy here is MMLU (Massive Multitask Language Understanding), a set of multiple-choice questions spanning 57 subjects from law to anatomy to abstract algebra. For years it was the number everyone quoted. The problem? Frontier models now score 90%+ on it, which means it\u0026rsquo;s basically maxed out and can\u0026rsquo;t tell good models apart from great ones anymore [2].\nThat\u0026rsquo;s why labs moved on to harder versions:\nMMLU-Pro — same idea, but with 10 answer choices instead of 4 (much harder to guess your way to a good score) and questions designed to require actual reasoning, not just recall. GPQA Diamond — PhD-level questions in biology, chemistry, and physics, written so carefully that non-expert PhD holders only score around 34% on them. That low human baseline is what makes it a useful yardstick — if a model clears 80%+, it\u0026rsquo;s doing something genuinely hard [2]. Humanity\u0026rsquo;s Last Exam (HLE) — 2,500 questions written by domain experts \u0026ldquo;at the boundary of human knowledge,\u0026rdquo; covering everything from STEM to humanities. Human experts average around 90% on it, while frontier models without external tools land somewhere around 37-47% [7]. It exists specifically because everything else got too easy. Coding and Math Tests For code, HumanEval used to be the go-to — 164 small Python problems, each checked against unit tests. It\u0026rsquo;s now sitting above 93% for top models, which again means it\u0026rsquo;s basically saturated [2]. The action has shifted to SWE-bench Verified, which throws models at real GitHub issues from popular open-source repos and checks whether their patch actually makes the test suite pass. Top models are now clearing somewhere in the 80-89% range on the \u0026ldquo;Verified\u0026rdquo; set, while the much harder \u0026ldquo;Pro\u0026rdquo; variant — multi-file, multi-language, real architectural complexity — keeps scores down in the 55-65% range [6].\nOn math, AIME (American Invitational Mathematics Examination) problems have become the standard torture test for \u0026ldquo;reasoning\u0026rdquo; models. The gap here is wild: general-purpose models often score in the 7-35% range, while dedicated reasoning models hit 90-100% on the same problems [16]. That single benchmark is probably the clearest evidence that \u0026ldquo;reasoning mode\u0026rdquo; (the kind of model that thinks step-by-step before answering) is a genuinely different capability, not just marketing.\nHere\u0026rsquo;s a quick cheat sheet for what these tests actually measure:\nBenchmark What It Tests How It\u0026rsquo;s Graded Status in 2026 MMLU General knowledge, 57 subjects Multiple-choice, auto-scored Saturated (90%+) [2] MMLU-Pro Harder knowledge + reasoning 10-option multiple-choice Active, differentiating GPQA Diamond PhD-level science reasoning Expert-written multiple-choice Active, human baseline ~34% [2] HumanEval Basic Python code generation Unit tests, pass@1 Saturated (93%+) [2] SWE-bench Verified Real GitHub bug fixes, end-to-end Automated test suite pass/fail Active, ~80-89% top models [6] AIME Competition-level math Exact numeric answer Active for non-reasoning models [16] Humanity\u0026rsquo;s Last Exam Expert questions across all fields Exact/short answer match Active, ~37-47% without tools [7] ARC-AGI-2 Novel visual pattern puzzles Exact grid match Largely unsolved The pattern you\u0026rsquo;ll notice: as soon as a benchmark gets \u0026ldquo;solved\u0026rdquo; (everyone scores 90%+), it stops being useful, and the field invents a harder one. This has happened to at least four major benchmarks in the last two years. It\u0026rsquo;s basically an arms race between test-makers and model-makers.\nMethod 2: Skip the Test, Just Ask Humans Standardized tests are great for measuring \u0026ldquo;did the model get the textbook answer right,\u0026rdquo; but they\u0026rsquo;re terrible at measuring \u0026ldquo;did the model give a helpful, well-written, pleasant-to-read answer.\u0026rdquo; For that, the AI world built something that looks a lot more like a dating app than an exam.\nThe most famous example is LMArena (formerly known as Chatbot Arena, run by the LMSYS group, and rebranded again to just \u0026ldquo;Arena\u0026rdquo; in early 2026) [1]. Here\u0026rsquo;s how it works:\nYou type a prompt — anything you want. Two different models, picked at random, both answer your prompt. Their names are hidden. You just see \u0026ldquo;Model A\u0026rdquo; and \u0026ldquo;Model B\u0026rdquo; side by side. You vote for the one you think gave the better response. Multiply that by millions of votes — the platform has racked up over 6 million of them — and you get an Elo-style rating for every model, the same statistical system used to rank chess players [1]. A model\u0026rsquo;s rating goes up when it beats a higher-rated opponent and goes down when it loses to a lower-rated one, so the math automatically accounts for \u0026ldquo;strength of schedule.\u0026rdquo;\nThere\u0026rsquo;s a wrinkle, though. Early on, people noticed that some models were winning votes just by being longer, using more bullet points, or sprinkling in more emoji — basically winning on style rather than substance. So in 2024 the platform introduced \u0026ldquo;style control,\u0026rdquo; which tries to mathematically separate \u0026ldquo;what the model said\u0026rdquo; from \u0026ldquo;how it said it,\u0026rdquo; and as of May 2025 this style-controlled score became the default ranking shown to visitors [1].\nThis arena approach is genuinely valuable because it captures things multiple-choice tests can\u0026rsquo;t: tone, formatting, how a model handles ambiguous or poorly-phrased questions, whether it\u0026rsquo;s annoyingly verbose, whether it refuses things it shouldn\u0026rsquo;t. But it\u0026rsquo;s also a popularity contest, and popularity contests can be gamed too — which brings us to the next method.\nMethod 3: The Robot Judging the Robot Here\u0026rsquo;s where it gets a bit recursive: a huge amount of modern LLM evaluation is done by\u0026hellip; other LLMs. This is called LLM-as-a-judge, and the idea is simple. You can\u0026rsquo;t write a unit test for \u0026ldquo;was this email polite enough\u0026rdquo; or \u0026ldquo;did this summary capture the key points.\u0026rdquo; So instead, you give a strong model (say, GPT-5 or Claude) the original question, the response you want to grade, and a rubric, and ask it to score the answer [10].\nThis is incredibly useful because it scales — you can grade thousands of open-ended responses overnight instead of paying humans to read them all. It\u0026rsquo;s the backbone of most custom evaluation pipelines companies build for their own products.\nBut, honestly, this is where it gets tricky. Research has documented a whole zoo of biases in LLM judges [10]:\nVerbosity bias — judges tend to prefer longer answers, even when a shorter one is actually clearer. Position bias — in side-by-side comparisons, the judge can favor whichever answer is shown first (or second). Self-enhancement bias — a model judging responses sometimes favors answers that \u0026ldquo;sound like itself.\u0026rdquo; Reproducibility issues — ask the same judge to grade the same answer twice, and you might get different scores, especially on fine-grained numeric scales rather than simple pass/fail calls. None of this makes LLM-as-a-judge useless — it just means you shouldn\u0026rsquo;t treat a single judge\u0026rsquo;s score as gospel. The smart move (and what most serious eval setups do) is to combine it with hard, verifiable checks wherever possible, and to spot-check the judge\u0026rsquo;s reasoning with actual humans periodically.\nThe Big Catch: Benchmarks Get Gamed Okay, here\u0026rsquo;s the part that honestly bugs me the most about this whole space. Benchmarks are public. The questions, and often the answers, are sitting on GitHub, in academic papers, on Reddit threads, in textbooks scraped for training data. So what happens when a model is trained on data that happens to include the test it\u0026rsquo;s later evaluated on?\nThis is called data contamination, and it\u0026rsquo;s a bigger deal than it sounds. Researchers have found that some models show accuracy gaps of up to 8 percentage points between \u0026ldquo;clean\u0026rdquo; versions of a benchmark and versions where the questions have been seen before [5]. Even weirder: just shuffling the order of multiple-choice answers on MMLU can swing a model\u0026rsquo;s accuracy by up to 13 percentage points [5]. Think about what that actually means — the model isn\u0026rsquo;t reasoning about the content nearly as much as we\u0026rsquo;d like to believe. It\u0026rsquo;s partly pattern-matching against memorized question formats.\nIt gets worse with indirect contamination too. One analysis found that roughly 42% of papers it reviewed may have inadvertently leaked benchmark data into GPT-3.5 and GPT-4 through API calls during evaluation — totaling something like 4.7 million benchmark samples across 263 different benchmarks [5]. Nobody did this maliciously; it\u0026rsquo;s just a side effect of how everyone evaluates models by\u0026hellip; calling the API of the model being evaluated, on data that sometimes ends up feeding back into future training.\nThis is part of why Hugging Face eventually retired its original Open LLM Leaderboard — built on the open-source lm-evaluation-harness [3] — and rebuilt it around harder, less-gameable tasks [4]. It\u0026rsquo;s also why a project like LiveBench exists: it releases a fresh batch of questions every single month, sourced from recent arXiv papers, news articles, and even movie synopses, specifically so there\u0026rsquo;s no way the questions could have leaked into training data yet [9]. Even with that protection, top models still score below 70% on it [9] — which tells you something about how much headroom is actually left versus how much of the \u0026ldquo;90%+\u0026rdquo; scores elsewhere are inflated by familiarity.\nMethod 4: Real-World Task Benchmarks If standardized tests are like exams, this category is more like an apprenticeship — drop the model into something resembling an actual job and see how far it gets.\nSWE-bench (mentioned earlier) is the flagship example for coding: real GitHub issues, real codebases, and the model has to read the code, figure out a fix, write a patch, and pass the existing test suite [6]. No multiple choice, no shortcuts — either the tests pass or they don\u0026rsquo;t.\nA newer and frankly more interesting angle comes from METR, a nonprofit that measures how long a task an AI agent can complete on its own. They call it the \u0026ldquo;time horizon\u0026rdquo; — specifically, the task duration (measured in how long it would take a skilled human) at which a model succeeds 50% of the time, or 80% of the time [8]. The wild part is the trend: this time horizon has been doubling roughly every seven months for the last several years [8]. Extrapolate that line and you get a pretty stark prediction about how much \u0026ldquo;real work\u0026rdquo; AI agents will be able to handle autonomously within a decade. Whether or not that trend holds exactly, it\u0026rsquo;s a genuinely different way to think about capability — not \u0026ldquo;did it get the right answer\u0026rdquo; but \u0026ldquo;how long of a leash can you give it before it gets lost.\u0026rdquo;\nThis category also includes things like ARC-AGI-2, a set of abstract visual puzzles designed to test whether a model can generalize to genuinely novel patterns rather than recalling something similar from training — and it remains stubbornly difficult for even the best models.\nOkay, But Is There a \u0026ldquo;3DMark\u0026rdquo; for LLMs? This is the question I was most curious about myself, since I\u0026rsquo;ve spent plenty of time running 3DMark [17] and Cinebench on PC builds. With GPUs, you run one app, get one number, and that number is directly comparable across reviews, forums, and years of hardware. Is there an equivalent for LLMs?\nSort of — but it\u0026rsquo;s split across a few tools rather than one app, and the analogy maps out like this:\nGPU/PC World What It Does LLM World Equivalent What It Does 3DMark Synthetic graphics workload, one comparable score MMLU / GPQA / AIME Synthetic question sets, one accuracy score [2] Cinebench Real rendering workload on CPU/GPU SWE-bench / agentic evals Real coding tasks, graded by execution [6] MLPerf (hardware) Vendor-neutral standard across chipmakers MLPerf Inference (LLM track) Vendor-neutral latency/throughput on real models like Llama 3.1 405B and GPT-OSS 120B [14] Forum/community rankings Crowd-sourced real-owner reports LMArena Crowd-sourced blind human votes, Elo-style [1] Price-to-performance charts Cost vs FPS Artificial Analysis Cost vs \u0026ldquo;Intelligence Index\u0026rdquo; vs tokens/sec, tracking 350+ models [15] The closest thing to an actual industry-standard tool is MLPerf, run by the nonprofit MLCommons — the same body that\u0026rsquo;s done hardware benchmarking for years. Their inference suite now includes dedicated LLM workloads (Llama 3.1 405B, Llama 2 70B, and as of the v6.0 release, an open-weight GPT-OSS 120B benchmark for math, science, and coding), measuring things like time-to-first-token and tokens-per-second under standardized conditions across different hardware vendors [14]. That\u0026rsquo;s genuinely apples-to-apples in the way 3DMark is.\nFor model quality comparisons rather than hardware, the closest thing to a one-stop dashboard is Artificial Analysis, which tracks over 350 models simultaneously across an \u0026ldquo;Intelligence Index\u0026rdquo; (a composite of multiple benchmarks), speed in tokens per second, and cost per million tokens — updated hourly [15]. It\u0026rsquo;s not a single downloadable app like 3DMark, but it serves the same function: one place to see the trade-off curve.\nThe honest answer to \u0026ldquo;is there a 3DMark for LLMs\u0026rdquo; is: not exactly one tool, but a layered stack of them, each operating at a different level of realism and cost:\nThe further up that stack you go, the more realistic the test gets — but also the more expensive, slower, and harder to compare across models it becomes. No single layer tells the whole story, which is exactly why every model release comes with about six different charts instead of one.\nTools You Can Run Yourself Here\u0026rsquo;s something I didn\u0026rsquo;t realize until I started poking around: you don\u0026rsquo;t need to be an AI lab to run these evaluations yourself. There\u0026rsquo;s a decent ecosystem of open tools for exactly this:\nlm-evaluation-harness by EleutherAI — the actual backend behind Hugging Face\u0026rsquo;s leaderboards, used by NVIDIA, Cohere, and others. It runs a model against dozens of standardized benchmarks (MMLU, GSM8K, BBH, and more) and spits out comparable numbers [3]. HELM from Stanford\u0026rsquo;s Center for Research on Foundation Models — goes beyond raw accuracy to measure things like bias, toxicity, and efficiency across a unified set of models and benchmarks, with a public leaderboard for comparing results [13]. Promptfoo — an open-source, YAML-configured tool for testing and comparing prompts/models side by side, including LLM-as-a-judge style grading. Used by hundreds of thousands of developers, and particularly strong for security/red-teaming style tests [11]. DeepEval — a Python-native framework with 50+ pre-built metrics for chatbots, RAG pipelines, and agents, designed to plug straight into your existing pytest suite [12]. The practical pattern most teams land on: run the big public benchmarks to get a general sense of where a model stands, then build a small custom eval set (even just 20-50 examples) that looks like your actual use case, and run every candidate model against that. That second part is the bit the public leaderboards can never do for you.\nSo How Should You Actually Pick a Model? After all this, here\u0026rsquo;s where I\u0026rsquo;ve landed. No single benchmark, leaderboard, or arena score should be the deciding factor — they\u0026rsquo;re all measuring slightly different things, and several of them are partially saturated or contaminated anyway.\nWhat actually works, in rough order:\nUse the big leaderboards (Artificial Analysis, LMArena) to shortlist 2-3 candidates for your price/speed/quality range — not to pick a single \u0026ldquo;winner.\u0026rdquo; Check the specific benchmark that matches your use case. If you\u0026rsquo;re building a coding tool, SWE-bench numbers matter way more than MMLU. If you\u0026rsquo;re building a customer-support bot, arena-style human preference scores probably matter more than AIME scores. Build a tiny eval set from your own real prompts — even 20 examples from actual user queries beats 10,000 generic benchmark questions for predicting how a model will perform for you. Run that eval set every time a new model version drops, before switching. Models get updated silently, and \u0026ldquo;better on the leaderboard\u0026rdquo; doesn\u0026rsquo;t always mean \u0026ldquo;better for your prompt.\u0026rdquo; Still do some good old vibe testing — just don\u0026rsquo;t only do that. Researchers have started studying \u0026ldquo;vibe-testing\u0026rdquo; seriously precisely because it captures real things (tone, formatting quirks, how a model handles your weird edge cases) that formal benchmarks miss [15]. The GPU benchmark comparison turns out to be more useful than I expected, actually — just not in the \u0026ldquo;one number to rule them all\u0026rdquo; sense. It\u0026rsquo;s useful in the sense that serious hardware reviewers never trust a single 3DMark score either. They run synthetic benchmarks, real game benchmarks, thermal tests, and power draw measurements, then form a judgment across all of it. LLM evaluation is heading the same direction — just a few years behind, and with the added headache that the \u0026ldquo;hardware\u0026rdquo; keeps getting silently swapped out from under you.\nSources Arena (AI platform) - Wikipedia AI Benchmarks 2026: Compare 300+ LLM Benchmarks \u0026amp; Tests - llm-stats.com lm-evaluation-harness - EleutherAI (GitHub) What\u0026rsquo;s going on with the Open LLM Leaderboard? - Hugging Face Towards Contamination Resistant Benchmarks - arXiv Top 7 Benchmarks That Actually Matter for Agentic Reasoning in LLMs - MarkTechPost Humanity\u0026rsquo;s Last Exam - Wikipedia Measuring AI Ability to Complete Long Tasks - METR LiveBench: A Challenging, Contamination-Free LLM Benchmark - GitHub LLMs-as-Judges: A Comprehensive Survey on LLM-based Evaluation Methods - arXiv Promptfoo Documentation DeepEval - The LLM Evaluation Framework HELM - Holistic Evaluation of Language Models - Stanford CRFM MLCommons Releases New MLPerf Inference v6.0 Benchmark Results LLM Leaderboard - Artificial Analysis AIME 2025 Benchmark: An Analysis of AI Math Reasoning - IntuitionLabs 3DMark Benchmark - UL Solutions ","permalink":"https://cloudmato.com/posts/how-to-test-and-benchmark-llm-models/","title":"How to Test an LLM: Benchmarks, Arenas, and Real Evals"},{"content":" Contact cloudmato.com is run by a small team that researches and writes every article on this site. We\u0026rsquo;d like to hear from you.\nGet in touch Email: cloudmato@gmail.com\nReach out if you\u0026rsquo;d like to:\nSuggest a topic for a future article Report an error, outdated information, or broken link Ask a question about something covered on the site We read every email and try to respond within a few days.\nFound an error? If something in an article is incorrect or out of date, please include the article URL and a short description of the issue — we\u0026rsquo;ll review and correct it.\n","permalink":"https://cloudmato.com/pages/contact/","title":"Contact"},{"content":" Privacy Policy This policy explains what data cloudmato.com collects, how it is used, and the choices you have. It applies to all language versions of this site.\nWho runs this site cloudmato.com is an independent blog. If you have questions about this policy, contact cloudmato@gmail.com. It may take some time to respond as I could be busy at my work.\nCookies and similar technologies This site, and the third-party services it uses, may set cookies or use similar technologies (such as local storage) in your browser to:\nRemember your preferences (for example, light/dark theme) Measure how the site is used (analytics) Show and measure advertising Google AdSense and advertising cloudmato.com uses, or may use, Google AdSense to display advertising. Google and its partners may use cookies and similar technologies to serve ads based on a visitor\u0026rsquo;s prior visits to this site or other sites on the internet.\nGoogle\u0026rsquo;s use of advertising cookies enables it and its partners to serve ads to you based on your visit to this site and/or other sites on the internet. You can opt out of personalized advertising by visiting Google\u0026rsquo;s Ads Settings. You can also opt out of a third-party vendor\u0026rsquo;s use of cookies for personalized advertising by visiting www.aboutads.info. For more detail on how Google uses information from sites that use its services, see How Google uses information from sites or apps that use our services. Analytics This site may use web analytics tools (such as Google Analytics or similar services) to understand how visitors use the site — for example, which pages are popular and how visitors arrived here. These tools may use cookies and collect information such as your IP address, browser type, device type, and pages visited. This data is aggregated and is not used to personally identify you.\nWhat we do not do We do not sell personal data. We do not require accounts, logins, or personal information to read this site. Comments or contact emails sent to us are used only to respond to you and are not shared with third parties except as required by law. Third-party links Articles on this site link to external sources for citations and further reading. We are not responsible for the privacy practices or content of external sites. We encourage you to review the privacy policy of any site you visit.\nChildren\u0026rsquo;s privacy This site is not directed at children under 13, and we do not knowingly collect personal information from children.\nChanges to this policy We may update this policy from time to time. The date below reflects the most recent revision.\nContact Questions about this policy? Email cloudmato@gmail.com.\nLast updated: June 2026.\n","permalink":"https://cloudmato.com/pages/privacy/","title":"Privacy Policy"},{"content":" Terms of Use By using cloudmato.com, you agree to the following terms. If you do not agree, please do not use this site.\nAbout the content cloudmato.com publishes articles about software development, web technology, and related topics. Articles are researched from publicly available sources, and cited sources are linked inline where relevant.\nContent is provided for informational purposes only and does not constitute professional, legal, financial, or security advice. Always verify critical information (especially around security, compliance, or production systems) against official documentation before acting on it.\nAccuracy We aim for accuracy and cite sources, but technology changes quickly and articles may become outdated. If you spot an error, please let us know and we will correct it.\nUse of content You are welcome to link to articles on this site. Reproducing full articles elsewhere without permission is not allowed. Short excerpts with attribution and a link back are fine. Code snippets shown in articles are provided as examples and may be used freely in your own projects. Third-party links and ads This site may display advertisements served by third parties, including Google AdSense, and may link to external websites. We do not control and are not responsible for the content, accuracy, or practices of third-party sites or advertisers. See our Privacy Policy for details on advertising and cookies.\nNo warranty This site and its content are provided \u0026ldquo;as is\u0026rdquo; without warranties of any kind, express or implied. We do not guarantee that the site will be error-free, secure, or continuously available.\nLimitation of liability To the maximum extent permitted by law, cloudmato.com and its author(s) are not liable for any damages arising from your use of, or inability to use, this site or its content.\nChanges to these terms We may update these terms from time to time. Continued use of the site after changes means you accept the updated terms.\nContact Questions about these terms? Email cloudmato@gmail.com.\nLast updated: June 2026.\n","permalink":"https://cloudmato.com/pages/terms/","title":"Terms of Use"},{"content":"Someone asked me this exact question last week, and it\u0026rsquo;s a good one because both setups look the same if you squint. A bunch of machines, some shared storage in the middle, work spread across nodes. So why does one get called \u0026ldquo;big data\u0026rdquo; and the other \u0026ldquo;microservices\u0026rdquo;? Are they just two words for the same cluster? Honestly, no. They\u0026rsquo;re built on opposite assumptions about one thing: where the data lives and who moves to whom.\nLet me unpack what Hadoop actually is first, then we\u0026rsquo;ll put the two side by side.\nWhat is Hadoop, really? Apache Hadoop is a framework for storing and processing datasets that are too big to fit on one machine — we\u0026rsquo;re talking terabytes to petabytes. It was built by the Apache Software Foundation and it follows a classic master-slave design across a cluster of cheap commodity machines [1]. The whole point is: instead of buying one giant expensive server, you buy 50 ordinary ones and make them work together.\nIt has three layers, and you really need all three to understand it:\nHDFS (Hadoop Distributed File System) — the storage layer. It takes your big files, chops them into block-sized chunks, and scatters those blocks across the disks of all the machines in the cluster [1]. YARN (Yet Another Resource Negotiator) — the resource manager. It decides which machine runs which piece of work, splitting resource management and job scheduling into separate daemons [1]. MapReduce — the original processing model. You write a \u0026ldquo;map\u0026rdquo; step and a \u0026ldquo;reduce\u0026rdquo; step, and the framework runs them in parallel across the cluster [1]. How HDFS spreads your data When you drop a 1 TB file into HDFS, it doesn\u0026rsquo;t sit on one disk. HDFS breaks it into blocks (128 MB each by default) and stores those blocks across the slave nodes. A master daemon called the NameNode keeps the metadata — filenames, which blocks belong to which file, and crucially, which machine holds each block. The actual blocks live on DataNodes, the slave daemons running on each machine [1].\nEach block also gets replicated (usually 3 copies on different machines), so if a disk dies — and with cheap hardware, disks die constantly — your data survives. This is the part people gloss over: Hadoop assumes failure is normal, not exceptional.\nHow MapReduce processes it Here\u0026rsquo;s the clever bit. In a typical Hadoop setup, the compute nodes and the storage nodes are the same machines. The MapReduce framework and HDFS run on the same set of nodes. This lets the framework schedule each task on the node where the data already physically sits, giving you very high aggregate bandwidth across the cluster [2].\nThat last sentence is the whole ballgame. Let me make it loud.\nThe one idea that defines Hadoop: move the code, not the data A design goal baked into Hadoop from day one: move computation to the data, rather than moving data to the computation [3]. This is called data locality, and it\u0026rsquo;s not a nice-to-have optimization — it\u0026rsquo;s the reason Hadoop exists.\nThink about it. Your code — the map function — is maybe a few kilobytes. The data block it needs to process is 128 MB. If you ship the data to where the code is, you\u0026rsquo;re pushing 128 MB across the network for every block, and you\u0026rsquo;ve got thousands of blocks. The network melts. Instead, Hadoop ships the tiny code to the machine that already holds the block, and runs it there, reading from the local disk [3].\nHadoop even ranks how good a task placement is:\nData-local — the task runs on the exact node holding the block. Best case. Rack-local — can\u0026rsquo;t get the exact node, so run it on another machine in the same server rack (fast network within a rack) [4]. Off-rack — worst case, data crosses racks. Hadoop tries hard to avoid this. Why does this matter so much? Because of something the storage world calls data gravity — large, active datasets attract applications to wherever they live, because moving the data becomes too slow and too expensive [5]. The storage itself isn\u0026rsquo;t the bottleneck; data movement is. Physics imposes limits you can\u0026rsquo;t engineer around — you can\u0026rsquo;t ship data as fast as you can create it [5]. Hadoop\u0026rsquo;s answer to data gravity was to stop fighting it. Bring the compute to the data. Surrender to gravity, basically.\nNow the microservices-on-Kubernetes setup Picture the other thing the question described: 10 microservices running in a Kubernetes cluster, all talking to some shared storage (say a network file system, or a cloud object store, or a managed database).\nKubernetes was fundamentally designed for stateless applications — services that don\u0026rsquo;t need to remember anything when they go down. That statelessness is what makes the magic work: declarative deployments, high availability, autoscaling, the ability to stop, restart, and clone a service with ease [6]. Your shopping-cart service, your auth service, your payment service — each one is a small box that handles a request and forgets about it.\nAnd the architectural principle here is the exact opposite of Hadoop\u0026rsquo;s. In a Kubernetes microservices setup, you deliberately decouple storage from compute. The storage layer is completely separated from the compute layer that Kubernetes manages [6]. Stateless app-server compute connects to data services that often run outside the cluster entirely [6].\nThere\u0026rsquo;s another rule that comes from the microservices world itself: services shouldn\u0026rsquo;t share a data store. Each service is supposed to own its own dataset, precisely to avoid hidden dependencies and accidental coupling between services [6]. So the phrase \u0026ldquo;10 microservices with shared storage\u0026rdquo; is already a bit of a smell — true microservices architecture pushes away from shared storage, toward each service owning its slice. (You can read more about this tension in Microsoft\u0026rsquo;s AKS microservices reference architecture.)\nWhen you do attach storage in Kubernetes, you usually wire the cluster to traditional infrastructure exposed over NFS, GlusterFS, or cloud file systems like Amazon EFS, Azure Files, and Google Cloud Filestore [6]. The compute and the storage are on different machines, connected by the network. The data is remote by design.\nSide by side: the actual difference So let\u0026rsquo;s stop being abstract. Here\u0026rsquo;s where they genuinely diverge.\nDimension Hadoop 10 microservices on K8s + shared storage Core job Process huge datasets in bulk Handle many independent requests Where data lives On the same nodes that compute (HDFS) On remote/shared storage, separate from compute Guiding principle Move code to the data [3] Decouple compute from storage [6] State Storage-centric, data is the system Compute is stateless, state pushed out [6] Unit of work A batch job split into map/reduce tasks A request/response per service What crosses the network Mostly tiny code; data stays local The data itself, on every operation Coupling Tightly co-located compute + storage Loosely coupled, each service independent Scaling target Throughput on massive data Concurrency and availability of requests Failure model Assumes disks die; replicates blocks Assumes pods die; reschedules stateless pods The headline: Hadoop co-locates compute and storage on purpose; Kubernetes microservices separate them on purpose. They aren\u0026rsquo;t two flavors of the same idea. They\u0026rsquo;re answers to two different questions.\nHadoop asks: \u0026ldquo;I have a mountain of data sitting still. How do I run computation over all of it without choking the network?\u0026rdquo;\nKubernetes microservices ask: \u0026ldquo;I have a flood of small, independent requests. How do I serve them reliably, scale each piece on its own, and survive crashes?\u0026rdquo;\nA scenario to make it click Say you run an e-commerce site.\nThe microservices on Kubernetes are your storefront in motion: a user clicks \u0026ldquo;add to cart,\u0026rdquo; the cart service handles it, the inventory service checks stock, the payment service charges the card. Ten small services, each scaling to traffic, each ideally owning its own data. Requests are tiny, the data each touches is tiny, and latency is everything. Shipping a few KB to a shared database over the network is totally fine here.\nNow, at 2 AM, you want to analyze every order from the last five years to find buying patterns. That\u0026rsquo;s 8 TB of historical logs. You\u0026rsquo;re not going to pull 8 TB through a microservice over the network — your network and your wallet would both die from data gravity [5]. This is the Hadoop-shaped job: park the data on HDFS, ship the analysis code to where the blocks live, crunch it in parallel, write out a summary. The data never moves; the code does.\nSame company, two completely different problems, two completely different architectures. They\u0026rsquo;re not competitors — honestly, they often coexist in the same building.\n\u0026ldquo;But my microservices process data too!\u0026rdquo; Sure they do. This is where it gets tricky, and where the comparison feels blurry. A microservice can absolutely read a file, transform it, and write it back. So what\u0026rsquo;s the real line?\nIt comes down to three things:\nVolume and locality. A microservice fetches a row, a document, a small blob — it pulls remote data to the compute. Hadoop refuses to do that at scale because the data is too big to move, so it sends compute to the data [3]. If your job\u0026rsquo;s bottleneck is \u0026ldquo;I can\u0026rsquo;t even read all the data fast enough,\u0026rdquo; you\u0026rsquo;re in Hadoop territory.\nGranularity of work. Microservices think in requests — short, isolated, low-latency. Hadoop thinks in jobs — long-running batch passes over an entire dataset where you happily wait minutes or hours [7]. MapReduce\u0026rsquo;s batch nature actually causes latency issues for real-time work, which is exactly why it\u0026rsquo;s the wrong tool for serving live requests [7].\nCoupling philosophy. Microservices want loose coupling and independent storage ownership so teams can move fast without stepping on each other [6]. Hadoop wants tight co-location of compute and storage so it can win the network fight. These goals literally contradict each other.\nSo a microservice doing a bit of data processing is still a microservice. It becomes a \u0026ldquo;big data system\u0026rdquo; when the data is the immovable center of gravity and the compute has to orbit it.\nA wrinkle worth knowing: even big data moved away from \u0026ldquo;pure\u0026rdquo; Hadoop I\u0026rsquo;d be doing you a disservice if I made it sound like Hadoop is still the default for big data. It isn\u0026rsquo;t anymore. Hadoop pioneered distributed computing, but it\u0026rsquo;s declining for new projects [7]. MapReduce writes intermediate results to disk between every step, which is slow, so Apache Spark came along and did the same kind of distributed processing largely in memory, dramatically speeding up iterative and interactive work [7].\nAnd the broader ecosystem is huge — Hadoop isn\u0026rsquo;t just MapReduce. It grew tools like:\nHive — a data-warehouse layer that lets you query HDFS data with a SQL-like language called HiveQL [8]. HBase — a distributed database for storing structured data in tables with billions of rows and millions of columns, sitting directly on HDFS [8]. Pig — a high-level platform using PigLatin to load, filter, and transform large datasets [8]. Interestingly, the modern cloud-data world has partly un-learned Hadoop\u0026rsquo;s core lesson. Tools like BigQuery and Snowflake deliberately separate storage from compute again — the same decoupling Kubernetes microservices use — because in the cloud, network bandwidth between object storage and compute got fast and cheap enough that data locality matters less than it did in 2008. So in a funny way, the pendulum swung from \u0026ldquo;co-locate everything\u0026rdquo; (Hadoop) toward \u0026ldquo;separate everything\u0026rdquo; (cloud data warehouses and microservices alike). Whether that holds as AI workloads explode data volumes again is an open question — some folks argue data gravity is roaring back and never really left [5].\nSo, are they the same thing or not? No. And here\u0026rsquo;s the cleanest way I can put it.\nA Kubernetes cluster running microservices is a request-serving machine. Its instinct is to keep compute stateless and lean, and to push data out to wherever it can be shared or owned independently. The network carries data to the compute, and that\u0026rsquo;s acceptable because each request touches a tiny amount [6].\nHadoop is a data-crunching machine. Its instinct is to nail the data down on the same disks that compute, because moving petabytes is a losing battle against physics. The network carries code to the data [3].\nIf you remember just one line: microservices move data to code; Hadoop moves code to data. Everything else — the daemons, the replication, the YARN scheduling, the StatefulSets — flows from that single decision.\nThey overlap in superficial ways (clusters, distribution, fault tolerance), which is why the question is so natural to ask. But the architectures are built on opposite bets about the most expensive thing in any distributed system: moving bytes across a wire.\nEnd\nSources Apache Hadoop Architecture - HDFS, YARN \u0026amp; MapReduce (TechVidvan) Apache Hadoop 3.3.5 – MapReduce Tutorial (Official Docs) Data Locality in Hadoop: The Most Comprehensive Guide (DataFlair) Introduction to Data Locality in Hadoop MapReduce (TechVidvan) What Is Data Gravity in Cloud Architecture? (simplyblock) Kubernetes Assimilation and the Need of Persistent Storage (Lightbits) Hadoop vs. Spark: Which One Should You Choose? (Back4App) Exploring the Hadoop Ecosystem: Hive, Pig, HBase, and More (Medium) ","permalink":"https://cloudmato.com/posts/what-is-hadoop-vs-microservices-kubernetes/","title":"What Is Hadoop, and Why It Isn't 10 Microservices on K8s"},{"content":"Everyone talks about ChatGPT and Claude like they just appeared one day. You type something, you get an answer, magic. But have you ever stopped to ask what it actually takes to make one of these things? Not the chat interface — the model itself. The thing that took months, hundreds of millions of dollars, and enough electricity to power a small town.\nI\u0026rsquo;ve been curious about this for a while, partly because the numbers are genuinely hard to believe until you sit with them. So I went digging through what\u0026rsquo;s actually known — the leaked architecture details, the hardware announcements, the data center buildouts. Some of it is public, some of it is well-sourced speculation, and some of it the labs keep deliberately vague. Let me walk you through what we actually know.\nThe short version: it\u0026rsquo;s three big stages, not one When people say a model was \u0026ldquo;trained,\u0026rdquo; they usually picture one giant computation. That\u0026rsquo;s wrong. Modern frontier models go through a multi-stage pipeline that OpenAI more or less formalized with InstructGPT back in 2022 [1]. The three stages are:\nPretraining — feed the model trillions of words and have it learn to predict the next token. This is the expensive part, the one that eats the GPU clusters for months. Supervised fine-tuning (SFT) — show it curated examples of good question-and-answer behavior so it learns to actually be helpful instead of just autocompleting. Reinforcement learning from human feedback (RLHF) — humans rank model responses, a separate \u0026ldquo;reward model\u0026rdquo; learns those preferences, and the main model gets nudged toward answers people prefer [1]. That last stage is the secret sauce that turns a raw text predictor into something that feels like it\u0026rsquo;s talking to you. Anthropic adds its own twist here with a method called Constitutional AI, where the model critiques itself against a written set of principles instead of relying purely on human labels.\nHonestly, this is where most explanations stop and where it gets interesting. So let\u0026rsquo;s go deeper on each piece.\nBefore anything trains: the data problem You can\u0026rsquo;t train a frontier model without data, and the scale here is the first thing that breaks your brain. GPT-3 was trained on roughly 300 billion tokens. By the time you get to Meta\u0026rsquo;s Llama 3, that number is over 15 trillion tokens [2]. GPT-4 reportedly sat around 13 trillion [3]. A token is roughly a sub-word chunk — \u0026ldquo;running\u0026rdquo; might be one token, or it might split into \u0026ldquo;run\u0026rdquo; and \u0026ldquo;ning\u0026rdquo; depending on the tokenizer.\nWhere does all this text come from? The backbone is Common Crawl, an open archive of web pages that releases fresh snapshots every month, measured in petabytes [2]. But here\u0026rsquo;s the thing nobody tells you: raw web data is garbage, and most of the work is cleaning it. Teams build elaborate filtering pipelines that do:\nLanguage identification — keep the languages you actually want Boilerplate removal — strip nav menus, cookie banners, ads Quality scoring — toss low-quality or spammy pages Deduplication — remove repeated content so the model doesn\u0026rsquo;t over-memorize Safety filtering — drop the genuinely nasty stuff [2] That deduplication step is sneakily one of the biggest bottlenecks. At trillion-token scale you can\u0026rsquo;t just compare every document to every other document — that\u0026rsquo;s computationally insane. So teams use tricks like MinHash LSH and Jaccard similarity to find near-duplicates approximately rather than exactly [4]. Then everything gets converted to UTF-8 bytes and run through Byte Pair Encoding to become the token IDs the model actually sees [2].\nThis stage is unglamorous and it takes serious engineering, but skip it and your billion-dollar training run learns from clickbait and comment-section sludge. Garbage in, garbage out — except the garbage costs $100 million to process.\nPretraining: where the GPUs go to work Now the expensive part. In pretraining, the model is shown a chunk of text and asked, over and over, billions of times: what\u0026rsquo;s the next token? It guesses, it\u0026rsquo;s wrong, the error gets pushed back through the network, the weights nudge slightly. Repeat that across trillions of tokens and the thing slowly learns grammar, facts, reasoning patterns, coding — all of it emerging from one stupidly simple objective.\nThe catch is that \u0026ldquo;billions of times across trillions of tokens\u0026rdquo; demands a frankly absurd amount of compute. Let\u0026rsquo;s talk hardware, because this is the part the user actually asked about.\nWhat GPT-4 ran on According to the widely-cited leaked details (OpenAI never officially confirmed these), GPT-4 was trained on roughly 25,000 NVIDIA A100 GPUs over about 90–100 days [3][5]. The model itself is reportedly around 1.8 trillion parameters using a Mixture-of-Experts design — 16 experts of about 111B parameters each, where only a couple activate per token instead of the whole network [5]. The raw compute came to roughly 2 × 10²⁵ FLOPs, and the training run alone cost an estimated $63 million [3].\nWhat GPT-5 reportedly runs on Jump forward and the hardware generation flips to NVIDIA\u0026rsquo;s Hopper chips. Reports peg GPT-5 training on around 50,000 H100 GPUs, totaling roughly 144 million GPU-hours, with an estimated cost north of $600 million [6]. NVIDIA itself has stated GPT-5 was trained on H100 and H200 GPUs [7]. The H200 is the upgrade that gave OpenAI more breathing room: 141 GB of memory at 4.8 TB/s bandwidth, versus the H100\u0026rsquo;s 80 GB [8].\nWhat Anthropic runs on Here\u0026rsquo;s where it gets genuinely different. Anthropic leans heavily on Amazon — not NVIDIA — through Project Rainier, one of the largest AI compute clusters on Earth, built on AWS\u0026rsquo;s custom Trainium2 silicon. Rainier came online in 2025 with nearly half a million Trainium2 chips, and AWS says Claude was expected to be running on more than 1 million Trainium2 chips by the end of that year [9]. That\u0026rsquo;s more than five times the compute Anthropic used for its previous models [9].\nThe architecture stitches these chips together with UltraServers — four servers of 16 Trainium2 chips each — connected internally over high-speed NeuronLinks and across clusters via Elastic Fabric Adapter networking [9]. And they\u0026rsquo;re not stopping: Anthropic committed to spending over $100 billion on AWS and securing up to 5 gigawatts of capacity across Trainium2, Trainium3, and beyond [10]. They\u0026rsquo;ve also signed a separate deal with Google and Broadcom for more custom chips [11]. When you hear \u0026ldquo;compute is the new oil,\u0026rdquo; this is what it looks like in practice.\nThe GPU generations, side by side Chip Architecture Memory Bandwidth Notable A100 Ampere 40/80 GB ~2 TB/s Trained GPT-4 (reportedly) [5] H100 Hopper 80 GB 3.35 TB/s The workhorse of 2023–24 [8] H200 Hopper 141 GB 4.89 TB/s Memory upgrade, same die [8] B200 Blackwell 180 GB 8 TB/s ~4x H100 training throughput, FP4 [12] Trainium2 AWS custom — NeuronLink fabric Anthropic\u0026rsquo;s Project Rainier [9] The jump from H100 to Blackwell\u0026rsquo;s B200 matters a lot. The B200 brings NVLink 5.0 at 1.8 TB/s per GPU (double the H100) and new FP4 precision tensor cores that deliver roughly 4x the training throughput on transformer models [12]. When you\u0026rsquo;re paying by the GPU-hour across tens of thousands of chips, a 4x speedup isn\u0026rsquo;t a nice-to-have — it\u0026rsquo;s the difference between a three-month run and a three-week one.\nWiring 100,000 GPUs together is its own nightmare Here\u0026rsquo;s a thing that surprised me: buying the GPUs is almost the easy part. Getting 100,000 of them to act like one computer is where the real engineering pain lives.\nA single 100,000 H100 cluster needs around 150 megawatts of data center capacity and burns through roughly 1.59 terawatt-hours of electricity a year — about $124 million in power costs alone at standard rates [13]. The servers themselves run around $4 billion [13]. That\u0026rsquo;s before you\u0026rsquo;ve trained anything.\nThen there\u0026rsquo;s networking. Every GPU has to constantly share its slice of the model with every other GPU, so the interconnect — InfiniBand or high-speed Ethernet — becomes the bottleneck. xAI\u0026rsquo;s Colossus supercomputer is the wild example here. They built it with 100,000 H100s in 122 days, then doubled it to 200,000 GPUs in another 92 days [14]. Their building block is a Supermicro liquid-cooled rack of 64 H100s, arranged in groups of 8 racks (512 GPUs) as mini-clusters [15]. Unusually, they skipped InfiniBand entirely and used NVIDIA\u0026rsquo;s Spectrum-X Ethernet fabric [14]. By late 2025 Colossus reportedly held 150,000 H100s, 50,000 H200s, and 30,000 GB200s [14].\nAnd at this scale, failures aren\u0026rsquo;t an edge case — they\u0026rsquo;re constant. With tens of thousands of GPUs running flat out for months, individual chips, cables, and nodes die regularly. That\u0026rsquo;s why labs lean so hard on checkpointing: periodically saving the entire model state so that when (not if) something fails, you restart from the last checkpoint instead of from zero [13]. Lose a week of a $600M run because you didn\u0026rsquo;t checkpoint and, well, you\u0026rsquo;re going to have a bad quarter.\nSo how long does it really take, start to finish? This is the question that I think most people get wrong, because they assume \u0026ldquo;training time\u0026rdquo; equals \u0026ldquo;the whole timeline.\u0026rdquo; It doesn\u0026rsquo;t. Let me break it into the phases that actually consume calendar time.\nThe pretraining run itself The headline compute run — the GPU-melting part — is on the order of 2 to 4 months for a frontier model. GPT-4\u0026rsquo;s was reportedly about 100 days on 25,000 A100s [5]. That\u0026rsquo;s the number you usually see quoted. But it\u0026rsquo;s also the smallest slice of the real timeline.\nEverything around it According to the GPT-4 leaks, the actual training took around 3 months, with roughly 6 additional months of safety testing layered on top before release [16]. So the compute is a third of the picture, at most.\nHere\u0026rsquo;s a rough end-to-end breakdown for a frontier model, based on what\u0026rsquo;s publicly known:\nPhase Roughly how long What\u0026rsquo;s happening Data collection \u0026amp; curation Months (often overlapping) Crawling, filtering, dedup, tokenizing trillions of tokens [2] Architecture \u0026amp; small-scale experiments Weeks to months Testing designs at small scale before committing Main pretraining run 2–4 months The big GPU cluster job [5] SFT + RLHF Weeks to a couple of months Teaching helpfulness and preferences [1] Safety testing \u0026amp; red teaming Months (~6 for GPT-4) Stress-testing for harm before release [16] Total, idea to launch Often ~9–18 months — That safety phase isn\u0026rsquo;t a rubber stamp. Anthropic\u0026rsquo;s red teaming, for instance, requires subject-matter and LLM experts to spend 100+ hours per domain probing the model for dangerous capabilities [17]. Before shipping Claude 3, their Trust \u0026amp; Safety team red-teamed for both text and image risks and brought in external testers [17]. Models from both Anthropic and OpenAI have also gone through pre-deployment testing with the US and UK AI Safety Institutes [17]. So when a lab says a model is \u0026ldquo;done training,\u0026rdquo; there\u0026rsquo;s often half a year of poking, prodding, and patching still ahead.\nAnd honestly, even after launch it\u0026rsquo;s never really finished. There are continued fine-tuning passes, the vision components (GPT-4\u0026rsquo;s image abilities were reportedly trained on another 2 trillion tokens after the text pretraining) [5], and the endless cycle of evaluation and iteration.\nWhy does any of this cost so much? Let me put the dollars in one place, because the scale is the whole story:\nGPT-4 training run: ~$63 million [3] GPT-5 training run: estimated $600M+ [6] A single 100K-GPU cluster: ~$4 billion in hardware, ~$124M/year in power [13] Anthropic\u0026rsquo;s AWS commitment: $100+ billion over a decade [10] The reason is almost embarrassingly simple. It\u0026rsquo;s compute. You\u0026rsquo;re renting (or buying) tens of thousands of the most in-demand chips on the planet, running them at full tilt for months, in data centers that draw as much power as a city. Every one of those GPU-hours costs money, every watt costs money, and every failed run that has to restart costs money. Stack that up across the full pipeline and the hundreds of millions stops looking crazy and starts looking inevitable.\nThere\u0026rsquo;s also a quieter cost most coverage ignores: the people. Data engineers building curation pipelines, researchers running small-scale experiments to de-risk the big run, human annotators ranking thousands of responses for RLHF, red teamers spending hundred-hour stretches trying to break the thing. The chips get the headlines, but a frontier model is as much a logistics and human-coordination feat as a hardware one.\nWhat this means if you\u0026rsquo;re not a trillion-dollar lab You\u0026rsquo;re probably not going to pretrain a 1.8-trillion-parameter model in your garage, and that\u0026rsquo;s kind of the point. The barrier to building a frontier model from scratch is now measured in billions of dollars and gigawatts of power — which is exactly why only a handful of organizations on Earth do it.\nBut here\u0026rsquo;s the more useful takeaway. Almost everything interesting you might build sits on top of that work — through an API, through fine-tuning a smaller open model, through retrieval and prompting. The trillion-token pretraining run is the part you rent, not the part you redo. The labs spent the $600 million so you can spend a few dollars per million tokens.\nWhat I find genuinely wild is how much of this is still half-secret. OpenAI never officially confirmed GPT-4\u0026rsquo;s architecture — most of what we \u0026ldquo;know\u0026rdquo; comes from leaks and well-sourced analysis [5]. Anthropic publishes a lot about safety methods but stays quiet on exact model sizes. So if you read this whole thing wanting a precise, confirmed spec sheet, I have to be honest: nobody outside those buildings has one. What we\u0026rsquo;ve got is leaks, hardware announcements, and the labs telling us how much money and silicon they\u0026rsquo;re throwing at the problem — and even that is enough to make your head spin.\nSources Pretraining: Breaking Down the Modern LLM Training Pipeline — MLOps Community Curating Trillion-Token Datasets — NVIDIA Technical Blog GPT-4 architecture, datasets, costs and more leaked — The Decoder Data Deduplication at Trillion Scale — Zilliz Blog GPT-4 Architecture, Infrastructure, Training Dataset, Costs — SemiAnalysis How Many GPUs to Train GPT-5 — CometAPI OpenAI\u0026rsquo;s GPT-5 was trained on NVIDIA H100 and H200 GPUs — NVIDIA Data Center NVIDIA H200 GPU: Specs, VRAM, Price — RunPod AWS activates Project Rainier — About Amazon Amazon announces additional $5B Anthropic investment — About Amazon Anthropic expands partnership with Google and Broadcom — Anthropic NVIDIA B200 vs H100 — Clarifai 100,000 H100 Clusters: Power, Network, Reliability — SemiAnalysis xAI Colossus supercomputer with 100K H100 GPUs comes online — Tom\u0026rsquo;s Hardware Inside the 100K GPU xAI Colossus Cluster — ServeTheHome GPT-4 Details Revealed — Patrick McGuinness Frontier Threats Red Teaming for AI Safety — Anthropic ","permalink":"https://cloudmato.com/posts/how-openai-anthropic-train-models/","title":"How OpenAI and Anthropic Actually Train Their Models"},{"content":"Everyone talks about HTTP/3 like it\u0026rsquo;s a free speed upgrade you flip on and forget. It mostly is — but \u0026ldquo;mostly\u0026rdquo; is doing a lot of work in that sentence. HTTP/3 is now used for roughly 35% of all web requests globally [1], so this isn\u0026rsquo;t a research toy anymore. The thing is, almost no one explains why it\u0026rsquo;s faster, where it actually loses to HTTP/2, and — the question nobody asks — whether your particular site even benefits. Let me walk through it the way I\u0026rsquo;d explain it to a friend over chai.\nThe one thing that actually changed Here\u0026rsquo;s the part people get wrong. They think HTTP/3 is a new way of writing requests, new headers, new methods, new everything. It isn\u0026rsquo;t.\nThe HTTP semantics — methods, status codes, headers — are identical to HTTP/2 and HTTP/1.1 [2]. A GET is still a GET. A 404 is still a 404. What changed is the transport: how those bytes get shoved across the internet. HTTP/1.1 and HTTP/2 both ride on TCP. HTTP/3 throws TCP out and runs on a brand-new protocol called QUIC, which itself runs on UDP [3].\nSo the difference is the how, not the what [4]. That single swap — TCP out, QUIC in — is the whole story. Everything good and everything annoying about HTTP/3 flows from it.\nTo understand why anyone bothered, you have to understand the problem TCP couldn\u0026rsquo;t shake.\nHead-of-line blocking: the bug that wouldn\u0026rsquo;t die This is the villain of the whole story, so let\u0026rsquo;s actually get it.\nHow we got here Back in HTTP/1.1, your browser opened a connection and sent one request at a time. Request, wait for response, next request. If you wanted parallelism, the browser opened 6 separate TCP connections per domain and juggled them. Wasteful, but it worked.\nHTTP/2 fixed that with multiplexing: many independent \u0026ldquo;streams\u0026rdquo; sharing a single TCP connection. Your CSS, your JS, your images all flow over one pipe at once, interleaved. Brilliant on paper. And on a clean network, genuinely great.\nBut here\u0026rsquo;s the catch nobody mentions. HTTP/2 multiplexes streams at the HTTP layer, while TCP underneath still insists on delivering one strictly ordered byte stream. TCP doesn\u0026rsquo;t know or care that you have 100 independent streams. It sees one sequence of bytes that must arrive in order.\nSo what happens when a single packet gets lost?\nThe stall If one packet drops, TCP halts everything that came after it — including bytes for completely unrelated streams — until that lost packet is retransmitted and arrives [5]. Your image1.jpg might be sitting there fully downloaded, ready to render, blocked not by its own network issue but because styles.css lost a packet [5].\nHTTP/2 didn\u0026rsquo;t actually solve head-of-line blocking. It just pushed it down a layer, from the application layer into the transport layer [5]. On a perfect network you\u0026rsquo;d never notice. On a flaky mobile connection with 2% packet loss? That one stall ripples across everything.\nThis is the part that genuinely surprised me when I first dug in. We spent years celebrating HTTP/2 multiplexing, and the dirty secret was that TCP could undo all of it with a single dropped packet.\nHow QUIC kills it QUIC makes each stream genuinely independent. Each QUIC stream keeps its own packet ordering. So when stream #5 loses a packet, only stream #5 stalls — streams #1 to #4 and #6 onward keep delivering immediately [5]. QUIC still retransmits the lost packet after a timeout, just like TCP, but it doesn\u0026rsquo;t drag everyone else down with it [3].\nThat\u0026rsquo;s the headline win. On a clean network it barely matters. On a lossy network it matters a lot.\nThe other wins (they\u0026rsquo;re real too) Head-of-line blocking gets the headlines, but QUIC bundles in a few more upgrades worth knowing.\nFaster handshakes With HTTP/2 over TCP, opening a secure connection means a TCP handshake and then a separate TLS handshake on top. QUIC folds the transport handshake and the cryptographic handshake into a single operation — connection established and encrypted in one flight [3].\nThe numbers people throw around: HTTP/3 offers 1-RTT connection setup and 0-RTT resumption, while HTTP/2 needs around 3 RTT for a secure connection [3]. Put plainly, HTTP/3 can set up connections up to ~50% faster than HTTP/2 [4]. If your users are far from your server — high latency — those saved round trips are real, felt time.\nEncryption isn\u0026rsquo;t optional anymore In HTTP/1.1 and HTTP/2, TLS sits on top as a separate layer you bolt on. In QUIC, TLS 1.3 is baked in as an integral component [3]. There\u0026rsquo;s no such thing as unencrypted HTTP/3. Honestly, I think that\u0026rsquo;s a good thing — it removes a whole class of \u0026ldquo;oops, plaintext\u0026rdquo; misconfigurations.\nConnection migration (the underrated one) This is my favorite feature and almost nobody talks about it.\nTCP identifies a connection by the classic 4-tuple: source IP, source port, destination IP, destination port. Change any of those — say your phone hops from Wi-Fi to 5G — and the IP changes, so the TCP connection dies and has to be rebuilt from scratch.\nQUIC uses a connection ID to identify a connection instead of relying on the 4-tuple [6]. So when your phone switches networks, QUIC just keeps the same connection ID and carries on [6]. Your video keeps playing, your download keeps downloading. Given how much of the internet is now mobile users walking around switching networks, this is a genuinely big deal.\nA quick side-by-side HTTP/1.1 HTTP/2 HTTP/3 Transport TCP TCP QUIC (over UDP) Multiplexing No (6 connections) Yes Yes Head-of-line blocking Yes (app layer) Yes (transport layer) Largely solved [5] Encryption TLS optional, bolted on TLS optional, bolted on TLS 1.3 mandatory, built in [3] Handshake to secure Multiple RTT ~3 RTT [3] 1-RTT / 0-RTT resume [3] Survives network switch No No Yes (connection migration) [6] So is HTTP/3 just\u0026hellip; better? Not always. Here\u0026rsquo;s where the marketing pages go quiet and I\u0026rsquo;ll be blunt. HTTP/3 is not always faster than HTTP/2 [7], and pretending otherwise sets you up for disappointment.\nThe CPU cost is real TCP has had decades of kernel-level optimization. QUIC runs in userspace over UDP, which is exactly what makes it easy to update and deploy — but it comes at a performance cost [8].\nBecause QUIC lives in userspace, it has to read ACKs through system calls constantly, and QUIC packets are tiny (~1KB), so processing all those packet-wise ACKs eats a large chunk of CPU [8]. The result: on fast, low-loss networks with plenty of bandwidth, an HTTP/3 transfer can burn more CPU time than the same transfer over HTTP/2 [8]. In high-bandwidth scenarios where CPU becomes the constraint, HTTP/3 can even show lower throughput than HTTP/1.1 [8].\nRead that again. On a fat, clean pipe, the \u0026ldquo;newer, faster\u0026rdquo; protocol can be slower because it\u0026rsquo;s CPU-bound. For a CPU-bound service, HTTP/3 can actually reduce your effective capacity [8].\nUDP sometimes just doesn\u0026rsquo;t get through QUIC rides on UDP, and some networks treat UDP as suspicious. Some firewalls and devices block unknown UDP protocols, so QUIC times out with no response from the server [9]. There are real reports of QUIC failing on certain carrier setups — like UDP being blocked on some 5G home internet [9]. Browsers handle this by quietly falling back to HTTP/2 over TCP, so users don\u0026rsquo;t usually break — but it does mean HTTP/3 isn\u0026rsquo;t a guaranteed win for everyone hitting your server.\nThere are also infrastructure gotchas. Some NAT devices don\u0026rsquo;t handle connection migration correctly, causing drops when the IP changes during a mobile handoff [6]. And for networks using anycast and ECMP routing, packets from the same QUIC connection can get routed to different backend servers after a NAT rebind or migration [6]. None of these are dealbreakers, but they\u0026rsquo;re the kind of thing that costs you an afternoon of debugging if you run your own edge.\nThe 0-RTT footgun Remember that lovely 0-RTT resumption? It has a sharp edge. 0-RTT early data can be replayed [10].\nThe mechanism uses a pre-shared key from a previous session, and because the early data is sent before the server confirms it\u0026rsquo;s talking to a fresh, live client, an attacker can grab a copy of that encrypted 0-RTT data and send it to the server again — seconds or minutes later [10]. The server sees a repeated request when only one was actually made [10]. That quietly breaks the idempotency assumption the web is built on.\nThe fix isn\u0026rsquo;t exotic: don\u0026rsquo;t allow non-idempotent requests (like POST) over 0-RTT. Some CDNs like Cloudflare already block non-idempotent 0-RTT requests by default, but other implementations such as nginx may not protect you out of the box [10]. So if you\u0026rsquo;re hand-rolling HTTP/3, this is on you to get right. Honestly, this is where it gets tricky and where a lot of people will misconfigure things [10].\nWho should actually use it? Now the real question. Let me break it down by who you are, because the answer genuinely differs.\nYou run a normal website behind a CDN Just turn it on. This is the easy case. If you\u0026rsquo;re on Cloudflare, Fastly, Cloudron, or similar, the CDN handles HTTP/3 at the edge — no recompiling, no kernel patches. On Cloudflare you literally toggle it in the dashboard and the edge starts advertising HTTP/3 to compatible clients via the Alt-Svc header [11].\nThat Alt-Svc header is how the whole thing bootstraps, by the way. The client first connects over HTTP/2, sees a response header like this, and knows it can try HTTP/3 next time [11]:\nalt-svc: h3=\u0026#34;:443\u0026#34;; ma=86400 The CDN absorbs the CPU overhead and the UDP-fallback headaches for you. There\u0026rsquo;s basically no downside. Do it.\nYou run your own nginx / origin server Worth it, but do it with eyes open. You\u0026rsquo;ll need an nginx build that supports QUIC — which historically meant building from source with the QUIC patch or using a supported fork [11]. A minimal config adds a second listen line with the quic parameter and advertises support:\nlisten 443 quic reuseport; listen 443 ssl; # keep TCP for fallback ssl_protocols TLSv1.3; add_header alt-svc \u0026#39;h3=\u0026#34;:443\u0026#34;; ma=86400\u0026#39;; Keep the regular TCP listen 443 ssl line too — that\u0026rsquo;s your fallback for clients and networks that can\u0026rsquo;t do QUIC [9]. And think hard about 0-RTT before enabling early data [10]. If you serve a CPU-heavy, high-bandwidth API on a clean datacenter network, benchmark it — you might find HTTP/2 is actually cheaper for you [8].\nYour users are mobile, far away, or on bad networks This is where HTTP/3 shines brightest. High latency means the faster handshake actually saves felt time [3]. Lossy connections mean per-stream independence keeps your page moving while one stream recovers [5]. And mobile users switching between Wi-Fi and cellular benefit directly from connection migration [6]. If your audience is \u0026ldquo;people on phones in places with patchy networks\u0026rdquo; — much of the real world — HTTP/3 is a clear win.\nYou run a CPU-bound, high-throughput backend on a clean network Pause. Benchmark before you commit. This is the one group that might not benefit. If your servers are CPU-constrained and your network is fast and reliable, the userspace QUIC overhead can eat into your throughput [8]. Measure with your real traffic, not someone\u0026rsquo;s blog benchmark (including this one).\nWhere adoption actually is Just so you know this isn\u0026rsquo;t speculative: HTTP/3 sits at around 35% of global requests as of late 2025 [1]. Growth has slowed — HTTP/2 and HTTP/3 each gained only fractions of a percentage point in 2025 compared to 2024 [1] — and adoption varies a lot by region, with 15 countries pushing more than a third of their requests over HTTP/3 [1]. All the major browsers support it, and the big CDNs have it ready to flip on. It\u0026rsquo;s mainstream now, not bleeding edge.\nThe honest summary? HTTP/3 is a real improvement for the conditions it was designed for — lossy networks, mobile users, high latency, lots of small requests. It is not a magic \u0026ldquo;make everything faster\u0026rdquo; switch, and on a CPU-bound service over a pristine network it can quietly cost you. Turn it on at the CDN where it\u0026rsquo;s free and safe. Be deliberate about it on your own origin. And whatever you do, don\u0026rsquo;t enable 0-RTT for POST requests and walk away.\nSources HTTP/3 Is at 35% Adoption — DEV Community What is QUIC and HTTP/3? — GeeksforGeeks What is HTTP/3? — Cloudflare HTTP/3 vs HTTP/2 Performance — DebugBear TCP head of line blocking — HTTP/3 explained Connection Migration — quic-go docs TIL: HTTP/3 Is Not Always Faster Than HTTP/2 — Ian Duncan LiteQUIC: Reducing CPU Overhead of QUIC — OpenReview How to Troubleshoot QUIC Protocol Issues Over UDP — OneUptime 0-RTT Replay: The High-Speed Flaw in HTTP/3 — InstaTunnel The End-to-End Playbook: Enabling HTTP/2 and HTTP/3 on Nginx + Cloudflare — DCHost ","permalink":"https://cloudmato.com/posts/understanding-http3-vs-http2-http1/","title":"Understanding HTTP/3: Is It Really Better Than HTTP/2?"},{"content":"Remember when GIFs were basically the only way to show something moving on a webpage without dragging in a full video player? Spinning loaders, little waving mascots, \u0026ldquo;loading\u0026hellip;\u0026rdquo; icons — all GIFs, all looking slightly blurry and oddly heavy for something so small. Turns out there\u0026rsquo;s been a much better option sitting right under our noses for years: the animated SVG. Let\u0026rsquo;s get into how you actually build one, and whether it really deserves to replace GIF.\nSo What Even Is an Animated SVG? An SVG (Scalable Vector Graphics) file isn\u0026rsquo;t a picture in the traditional sense — it\u0026rsquo;s a text file written in XML that describes shapes using math: \u0026ldquo;draw a circle here, this big, this color\u0026rdquo; or \u0026ldquo;draw a path that goes from point A to point B.\u0026rdquo; Because a browser renders that math live, every time, an SVG looks razor sharp whether it\u0026rsquo;s 16px or 1600px wide [1].\nNow here\u0026rsquo;s the part that makes this whole article possible: since an SVG is just markup describing shapes, you can change the values in that markup over time. Move the circle\u0026rsquo;s cx attribute from 50 to 450, rotate a rectangle, fade a path\u0026rsquo;s opacity from 0 to 1 — do any of that gradually, and boom, you\u0026rsquo;ve got an animation. No frames, no separate images, just numbers changing.\nThis is fundamentally different from a GIF, which is a stack of fully-rendered raster images flipped quickly one after another, like a flipbook. We\u0026rsquo;ll come back to why that distinction matters a lot.\nThree Ways to Actually Animate an SVG There isn\u0026rsquo;t just one way to do this, and honestly that\u0026rsquo;s both a blessing and a curse — too many options can be paralyzing. Here\u0026rsquo;s the breakdown.\nMethod 1: SMIL — animation baked right into the file SMIL (Synchronized Multimedia Integration Language) is SVG\u0026rsquo;s native animation syntax. You add special elements like \u0026lt;animate\u0026gt;, \u0026lt;animateTransform\u0026gt;, and \u0026lt;animateMotion\u0026gt; directly inside your shapes, and the SVG animates itself — no CSS, no JavaScript, nothing extra needed.\n\u0026lt;svg width=\u0026#34;200\u0026#34; height=\u0026#34;200\u0026#34; viewBox=\u0026#34;0 0 200 200\u0026#34;\u0026gt; \u0026lt;circle r=\u0026#34;20\u0026#34; cx=\u0026#34;50\u0026#34; cy=\u0026#34;100\u0026#34; fill=\u0026#34;#4a90e2\u0026#34;\u0026gt; \u0026lt;animate attributeName=\u0026#34;cx\u0026#34; from=\u0026#34;50\u0026#34; to=\u0026#34;150\u0026#34; dur=\u0026#34;1s\u0026#34; repeatCount=\u0026#34;indefinite\u0026#34; /\u0026gt; \u0026lt;/circle\u0026gt; \u0026lt;rect x=\u0026#34;80\u0026#34; y=\u0026#34;20\u0026#34; width=\u0026#34;40\u0026#34; height=\u0026#34;40\u0026#34; fill=\u0026#34;orange\u0026#34;\u0026gt; \u0026lt;animateTransform attributeName=\u0026#34;transform\u0026#34; attributeType=\u0026#34;XML\u0026#34; type=\u0026#34;rotate\u0026#34; from=\u0026#34;0 100 40\u0026#34; to=\u0026#34;360 100 40\u0026#34; dur=\u0026#34;2s\u0026#34; repeatCount=\u0026#34;indefinite\u0026#34; /\u0026gt; \u0026lt;/rect\u0026gt; \u0026lt;/svg\u0026gt; The from/to/dur/repeatCount pattern is pretty readable once you see it a couple of times. You can even trigger animations on events with begin=\u0026quot;click\u0026quot; [3].\nHere\u0026rsquo;s where it gets messy though. Back around 2015-2016, Chrome\u0026rsquo;s team announced they wanted to deprecate SMIL entirely and push everyone toward CSS Animations and the Web Animations API instead [2]. Developers pushed back hard — turns out things like path morphing and animating SVGs embedded via \u0026lt;img\u0026gt; tags simply have no CSS equivalent. Chrome backed off, SVG 2 kept every animation element, and SMIL is still alive and well in every major modern browser today [4]. So no, SMIL is not dead, despite the scare. It\u0026rsquo;s just\u0026hellip; not the recommended first choice anymore for most everyday animations, mainly because the syntax feels foreign if you already think in CSS.\nMethod 2: CSS animations — the modern default This is where most people land, and for good reason — if you already know CSS, you basically already know how to animate SVG. You can target SVG elements with regular selectors and animate properties like transform, opacity, fill, and a bunch of SVG-specific properties [1][5].\n.gear { transform-origin: center; animation: spin 3s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } The real party trick of CSS-based SVG animation, though, is the classic \u0026ldquo;line drawing\u0026rdquo; effect — you\u0026rsquo;ve definitely seen logos or signatures that look like they\u0026rsquo;re being drawn by an invisible pen. That\u0026rsquo;s done with two properties: stroke-dasharray and stroke-dashoffset [6].\nHere\u0026rsquo;s the gist: stroke-dasharray lets you turn a solid line into a dash pattern. If you set it to a number equal to (or bigger than) the total length of the path, you essentially create one dash that\u0026rsquo;s the entire line, followed by one gap that\u0026rsquo;s also the entire line. Then stroke-dashoffset shifts where that dash starts. Animate the offset from the full length down to zero, and the \u0026ldquo;dash\u0026rdquo; appears to slide into place — which looks exactly like the line is being drawn [7].\n.signature-path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: draw 3s ease forwards; } @keyframes draw { to { stroke-dashoffset: 0; } } The only annoying bit is figuring out the actual path length (1000 in this example is just a placeholder — you\u0026rsquo;d typically grab the real value with path.getTotalLength() in JavaScript or just eyeball a number bigger than the path and adjust).\nMethod 3: JavaScript libraries — when you need real control CSS keyframes are great until you need something like \u0026ldquo;animate this shape along a wiggly custom path while it also rotates and changes color halfway through, then morphs into a completely different shape.\u0026rdquo; At that point, reach for a JavaScript animation library. GSAP (GreenSock) is the go-to here — it can animate basically anything JavaScript can touch, with a MotionPathPlugin specifically for moving objects along SVG paths, plus tools for \u0026ldquo;drawing\u0026rdquo; strokes and morphing one path into another [8].\ngsap.to(\u0026#34;#rocket\u0026#34;, { duration: 4, repeat: -1, ease: \u0026#34;power1.inOut\u0026#34;, motionPath: { path: \u0026#34;#flightPath\u0026#34;, align: \u0026#34;#flightPath\u0026#34;, autoRotate: true, }, }); It\u0026rsquo;s overkill for a simple hover effect on an icon, but for hero-section illustrations or scroll-triggered storytelling animations, it\u0026rsquo;s the right tool.\nQuick Comparison of the Three Methods Method Best for Pros Cons SMIL Self-contained animations baked into the SVG file No external dependencies; works even when SVG is used as \u0026lt;img\u0026gt; Syntax feels unfamiliar; was nearly deprecated once (caused trust issues) CSS Hover effects, loops, simple transitions, line-drawing Familiar syntax, GPU-accelerated, easy prefers-reduced-motion support Limited for complex path morphing or sequencing JavaScript (GSAP etc.) Complex sequences, scroll triggers, path morphing Total control, great browser-consistency, rich plugin ecosystem Adds a script dependency; needs the SVG to be accessible in the DOM Or\u0026hellip; skip the code entirely If hand-writing keyframes isn\u0026rsquo;t your idea of fun, tools like SVGator give you a timeline-based editor — drag your SVG in, set keyframes visually, and export clean CSS, SMIL, JavaScript, or even Lottie JSON [13]. It\u0026rsquo;s not going to replace knowing the fundamentals, but for quick wins (animated logos, simple icon transitions) it\u0026rsquo;s genuinely useful, especially if you\u0026rsquo;re a developer who isn\u0026rsquo;t great with visual timing and easing curves.\nGetting It Onto Your Page Without Breaking the Animation This part trips people up constantly. Where you place your SVG in HTML actually determines whether your animation works at all.\nEmbedding method CSS animation works? SMIL works? JS-driven animation works? Notes Inline (\u0026lt;svg\u0026gt; directly in HTML) ✅ Yes ✅ Yes ✅ Yes Most flexible — it\u0026rsquo;s part of the DOM, no extra request \u0026lt;img src=\u0026quot;...\u0026quot;\u0026gt; ✅ Yes (if defined inside the SVG file) ✅ Yes ❌ No Browser treats it as an opaque image; can\u0026rsquo;t be styled from your page\u0026rsquo;s CSS or touched by your page\u0026rsquo;s JS \u0026lt;object data=\u0026quot;...\u0026quot;\u0026gt; ✅ Yes ✅ Yes ✅ Yes (with extra work) Loads as a separate document; JS can reach in, but it\u0026rsquo;s clunkier CSS background-image ✅ Yes (if defined inside the file) ✅ Yes ❌ No Same limitations as \u0026lt;img\u0026gt; The short version: if your animation logic lives entirely inside the SVG file itself (SMIL, or CSS written inside a \u0026lt;style\u0026gt; tag in the SVG), then \u0026lt;img\u0026gt;, \u0026lt;object\u0026gt;, or inline all work fine for the visual animation. But the moment you want your page\u0026rsquo;s CSS or JavaScript to reach in and control the SVG — say, pausing an animation on hover, or changing colors based on a theme toggle — you need it inline [9][10].\nThis is also exactly why SMIL still matters in 2026: it\u0026rsquo;s the only animation method that survives being dropped into a plain \u0026lt;img\u0026gt; tag, which is the simplest and most cache-friendly way to use an SVG.\nOkay, But Is It Actually Better Than a GIF? Here\u0026rsquo;s where I think most \u0026ldquo;SVG vs GIF\u0026rdquo; articles get a little breathless, so let me try to keep this grounded. For most use cases — yes, animated SVG wins, and it\u0026rsquo;s not close. But \u0026ldquo;better\u0026rdquo; depends on what you\u0026rsquo;re animating.\nSara Soueidan ran an actual head-to-head comparison years ago that\u0026rsquo;s still the reference point people cite, and the numbers are kind of wild [9]:\nA simple animated GIF came in at 19.91 KB, while the equivalent SVG animation was 0.249 KB — roughly 80x smaller for the same effect. A real homepage swapped its hero animations from GIF to SVG and dropped from 1.6 MB to 389 KB, cutting load time from 8.75 seconds to 412 milliseconds. Cloudinary\u0026rsquo;s comparison backs this up too, generally putting SVG animations at 10-50x smaller than equivalent GIFs [10]. Why such a massive gap? It comes back to that \u0026ldquo;flipbook vs. math\u0026rdquo; distinction from earlier.\nBeyond size, there\u0026rsquo;s a bunch of stuff GIFs simply can\u0026rsquo;t do:\nTransparency: GIFs only support binary transparency — a pixel is either fully visible or fully invisible. There\u0026rsquo;s no in-between, which creates that ugly \u0026ldquo;halo\u0026rdquo; edge when a GIF sits on a colored background. SVG supports full alpha-channel transparency, so soft shadows and fades look correct on any background [9]. Control: You can pause, restart, speed up, or reverse an SVG animation with a line of JS or CSS. A GIF, once exported, is locked — you can\u0026rsquo;t pause it without extra tricks [9]. Interactivity: Want the animation to react to a hover, a click, or a scroll position? SVG (especially inline SVG) lives in the DOM, so any element can become interactive. GIF pixels can\u0026rsquo;t [10]. Color fidelity: GIFs are limited to a 256-color palette, which causes visible banding on gradients. SVG has no such limit. SEO and accessibility: Inline SVG text content (like \u0026lt;title\u0026gt; and \u0026lt;desc\u0026gt; elements) is readable by screen readers and indexable by search engines, whereas a GIF is just an opaque blob with whatever alt text you bothered to write [9]. Where GIFs Still Win (Being Honest) I don\u0026rsquo;t want to pretend GIF is useless now — that wouldn\u0026rsquo;t be fair, and it\u0026rsquo;s not true.\nPhotographic or highly complex content. SVG is a vector format. If you\u0026rsquo;re animating a photo-realistic scene, hundreds of overlapping gradients, or something with tons of fine detail, describing all of that as mathematical shapes can actually produce a bigger file than a compressed GIF (or better yet, a short video) [10]. Universal, dumb-simple embedding. GIFs work absolutely everywhere — emails, old chat apps, Markdown previews, places where SVG support is patchy or actively blocked for security reasons (SVGs can contain embedded scripts, which is exactly why some platforms strip or sandbox them). Zero animation knowledge required. Someone can make a GIF from a video clip in 30 seconds with free tools. Making a good animated SVG requires at least a little familiarity with CSS or SMIL. So if you\u0026rsquo;re sending a quick reaction GIF in Slack, nobody\u0026rsquo;s reaching for an SVG animation editor. But for anything that lives on your website — icons, logos, illustrations, UI feedback — SVG is almost always the better engineering choice.\nWhat About Lottie? And Video? Since we\u0026rsquo;re on the topic, two other formats deserve a mention because people often confuse them with \u0026ldquo;just use SVG.\u0026rdquo;\nLottie is a JSON format created by Airbnb to bring complex After Effects animations to apps and the web without baking them into video [11]. It\u0026rsquo;s fantastic for very elaborate motion design — think onboarding animations with dozens of moving parts. But it comes with a real cost: the lottie-web player library itself is roughly 60 KB gzipped, which is a tax you pay even for a single small icon — a tax a CSS-animated SVG simply doesn\u0026rsquo;t have [12]. Interestingly, SVGator\u0026rsquo;s own export data from their user base shows about 60-70% of users export to SVG versus only 5-7% to Lottie, which tells you something about where the industry\u0026rsquo;s preferences sit for typical web use [11].\nVideo (MP4, WebM) is still the right call for full-motion footage — actual camera recordings, complex photographic animations, anything where \u0026ldquo;vector shapes\u0026rdquo; just doesn\u0026rsquo;t make sense as a description of the content [14].\nFormat Best for Typical size for a small animation Needs extra library? GIF Quick, universal, simple loops; email/chat Large (hundreds of KB+) No Animated SVG Icons, logos, UI motion, illustrations Tiny (often under 50 KB) No (CSS/SMIL) Lottie Complex motion-design animations from After Effects Small JSON, but ~60KB player Yes (lottie-web) Video (MP4/WebM) Real footage, photo-realistic motion Medium-large No, but needs a player Don\u0026rsquo;t Skip Accessibility — prefers-reduced-motion This is the part that\u0026rsquo;s easy to forget when you\u0026rsquo;re excited about your fancy new spinning, morphing logo. Roughly 70 million people have vestibular disorders, and large or continuous motion can genuinely cause dizziness or nausea for some of them — not just mild annoyance [15].\nThe good news is that respecting this is one CSS media query away:\n.logo-animation { animation: spin 3s linear infinite; } @media (prefers-reduced-motion: reduce) { .logo-animation { animation: none; } } This checks the user\u0026rsquo;s OS-level setting (every major OS has one now) and lets you either remove the animation entirely or swap it for something gentler — a simple fade instead of a spin, for example [16]. If your animation is purely decorative, killing it outright is fine. If it\u0026rsquo;s conveying information (like a progress spinner), consider a non-animated equivalent — a static percentage or a simple pulse that stops after a few seconds rather than looping forever [17].\nFor SMIL-based animations, you don\u0026rsquo;t get a native media query hook the same way, which is honestly another point in CSS\u0026rsquo;s favor for anything user-facing.\nReal-World Places You\u0026rsquo;ll Actually Use This To bring this down to earth, here\u0026rsquo;s where animated SVG genuinely shows up in production sites and apps:\nLoading spinners and progress indicators — lightweight, GPU-friendly, and there\u0026rsquo;s a whole library of ready-made SVG spinners you can drop in [17]. Animated logos on landing pages — the \u0026ldquo;drawing\u0026rdquo; effect using stroke-dasharray is everywhere for this. Micro-interactions — a checkbox that draws a checkmark, a heart icon that \u0026ldquo;pops\u0026rdquo; on like, a hamburger menu that morphs into an X. Data visualizations — charts where bars grow or lines draw themselves as they come into view. Animated favicons — subtle motion in the browser tab to indicate background processing, like an export or upload still running. Onboarding illustrations — gentle floating/bobbing elements that add personality without weighing down the page. Notice what these have in common: they\u0026rsquo;re all vector-friendly content — icons, shapes, line art. That\u0026rsquo;s the sweet spot.\nSo, Which Should You Actually Pick? If you\u0026rsquo;re building for the web and your content is icons, logos, illustrations, or UI motion — animated SVG is the better default, full stop. It\u0026rsquo;s smaller, sharper, controllable, accessible, and doesn\u0026rsquo;t need a heavyweight library. Start with CSS for anything simple (hover states, loops, line-drawing), reach for SMIL if you need the animation to survive being dropped in as a plain \u0026lt;img\u0026gt;, and bring in GSAP only when you\u0026rsquo;re doing something genuinely complex like path morphing or scroll-driven sequences.\nGIF isn\u0026rsquo;t dead — it\u0026rsquo;s just been demoted to the \u0026ldquo;universal fallback for non-vector content\u0026rdquo; role, which honestly is probably where it belonged all along. The next time you\u0026rsquo;re about to export a GIF for a website icon, ask yourself: could this just be a few lines of CSS instead? More often than you\u0026rsquo;d think, the answer is yes.\nSources How to Animate SVG with CSS? - GeeksforGeeks How to animate SVG with CSS: Tutorial with examples - LogRocket Blog A Guide to SVG Animations (SMIL) | CSS-Tricks Is SMIL dead in 2022? Nope | SVG Backgrounds Intent to deprecate: SMIL - Chromium blink-dev stroke-dasharray | CSS-Tricks How SVG Line Animation Works | CSS-Tricks SVG | GSAP Animated SVG vs GIF [CAGEMATCH] - Sara Soueidan GIF vs SVG: A Developer\u0026rsquo;s Guide to Understanding Animation Formats | Cloudinary Why Users Prefer SVG Over Lottie - SVGator Lottie format vs SVG format - SVGator How to Add Animated SVG to Your Website | SVGator Help SVG Animation Benefits: Performance, Scalability, And SEO Explained - SVGator Accessible Animations in React with \u0026ldquo;prefers-reduced-motion\u0026rdquo; - Josh W. Comeau prefers-reduced-motion | CSS-Tricks GitHub - n3r4zzurr0/svg-spinners Should I use img, object, or embed for SVG files? - GeeksforGeeks ","permalink":"https://cloudmato.com/posts/how-to-create-animated-svg-vs-gif/","title":"How to Create Animated SVG (And Is It Better Than GIF?)"},{"content":"Ever clicked a button in a React app and just\u0026hellip; trusted that the UI would update correctly? Yeah, me too, for years. I never really stopped to think about what happens between setState and the pixels changing on screen — until I started debugging a component that was re-rendering five times for a single click. That rabbit hole led me straight into Fiber, lanes, and the surprisingly long history of how React decides when to actually re-render your app.\nThis article is that rabbit hole, organized. We\u0026rsquo;ll go through how React actually works under the hood, what React Fiber is and why it exists, and then walk through how batching — the thing that controls how many times your component re-renders — has changed release after release.\nWhat Actually Happens When You Call setState? Here\u0026rsquo;s the thing almost nobody explains clearly: calling setState (or a state setter from useState) doesn\u0026rsquo;t immediately change anything on the screen. It just tells React \u0026ldquo;hey, something changed, you might want to look at this again.\u0026rdquo;\nWhat follows is a multi-step process:\nTrigger — something happens (an event, a network response, a timer) that calls a state setter. Render — React calls your component functions again to figure out what the new UI should look like. This produces a tree of React elements (often loosely called the \u0026ldquo;virtual DOM\u0026rdquo;). Reconciliation — React compares the new tree with the previous one and figures out the minimal set of changes needed. Commit — React applies those changes to the actual DOM, runs effects, and the browser paints the result. The term \u0026ldquo;virtual DOM\u0026rdquo; gets thrown around a lot, and honestly it\u0026rsquo;s a bit of a misnomer at this point. What React actually maintains internally these days is a tree of Fiber nodes — which is a much richer structure than a simple snapshot of the DOM. We\u0026rsquo;ll get to that in a second, because it\u0026rsquo;s really the heart of this whole article.\nReconciliation: React\u0026rsquo;s Diffing Trick Before Fiber even comes into the picture, it helps to understand why React can update the DOM efficiently at all. Comparing two arbitrary trees node-by-node is, in computer science terms, an expensive problem — naive tree diffing is roughly O(n³) for n nodes. That\u0026rsquo;s way too slow for a UI library that needs to run on every keystroke.\nSo React uses a heuristic-based algorithm with a couple of simplifying assumptions [5]:\nDifferent element types produce different trees. If a \u0026lt;div\u0026gt; becomes a \u0026lt;span\u0026gt;, React doesn\u0026rsquo;t bother diffing their children — it just tears down the old one and builds the new one from scratch. Same element type, same position? React keeps the underlying DOM node and just updates the changed attributes/props. Lists need keys. When you render a list with .map(), React uses the key prop to match items between renders. Without stable keys (or worse, using array index as a key when the list can reorder), React can get confused about which item is which — leading to weird bugs like form inputs retaining the wrong value after a reorder. This is the part of React\u0026rsquo;s reconciliation process that most tutorials cover [5]. But reconciliation by itself doesn\u0026rsquo;t explain how React manages to do all this work without freezing the browser on a big update. That\u0026rsquo;s where Fiber comes in — and it\u0026rsquo;s a much bigger deal than most people realize.\nEnter Fiber: The Rewrite That Changed Everything Back before React 16, React used what\u0026rsquo;s now called the \u0026ldquo;stack reconciler.\u0026rdquo; It worked, but it had one fundamental problem: it was recursive and synchronous. Once React started rendering a tree, it couldn\u0026rsquo;t stop until it was done — the JavaScript call stack just kept growing until the whole tree was processed [3][4].\nFor small apps, that\u0026rsquo;s fine. You\u0026rsquo;d never notice. But for big component trees — think a data grid with thousands of rows, or a complex dashboard — that synchronous render could take long enough to block the main thread for tens of milliseconds. Animations would stutter, scrolling would jank, and typing into an input could feel sluggish because the browser couldn\u0026rsquo;t process anything else until React finished its work.\nIn September 2017, React 16 shipped with a complete rewrite of this core algorithm, called Fiber [2]. Meta\u0026rsquo;s engineering team described it as an \u0026ldquo;API-compatible rewrite\u0026rdquo; — meaning your component code didn\u0026rsquo;t need to change, but everything happening underneath it did [2].\nWhat Is a Fiber, Really? The cleanest way I\u0026rsquo;ve seen it described (from React core contributor Andrew Clark\u0026rsquo;s own notes) is that a fiber is a \u0026ldquo;virtual stack frame\u0026rdquo; [3]. Instead of relying on the JavaScript call stack — which executes top to bottom with no pausing — React builds its own data structure that mimics a stack, but one it fully controls.\nEach fiber is a plain JavaScript object representing a unit of work — a component, basically — and it holds:\ntype and key — what kind of component or DOM element this is child, sibling, and return pointers — forming a linked-list representation of the tree (instead of a typical nested-array tree) pendingProps and memoizedProps — the new props coming in vs. the props from last time, used to check if anything actually changed alternate — a pointer to the \u0026ldquo;other version\u0026rdquo; of this fiber (more on this in a second) That alternate pointer is honestly the cleverest part. React keeps two trees in memory at once: the current tree (what\u0026rsquo;s actually on screen right now) and the workInProgress tree (what React is currently building for the next update) [4]. When the work-in-progress tree is complete, React just swaps a pointer — the work-in-progress becomes the current tree. This is sometimes called \u0026ldquo;double buffering,\u0026rdquo; and it\u0026rsquo;s the same trick video games use to avoid showing half-drawn frames.\nBecause each fiber is its own little unit of work, React can process the tree node-by-node, and after finishing each node, stop and ask: \u0026ldquo;do I have time to keep going, or should I yield back to the browser?\u0026rdquo; [3][4]. That single ability — to pause, yield, and resume — is what makes everything else (concurrent rendering, transitions, automatic batching) possible.\nRender Phase vs. Commit Phase React splits all this work into two phases with very different rules:\nRender phase — React walks the workInProgress tree, calls your components, and figures out what changed. This phase is pure (no DOM mutations, no side effects) and interruptible — React can pause it, throw away the work, or restart it if a more urgent update comes in [3][4]. Commit phase — React takes the finished work and actually mutates the real DOM, runs layout effects, and updates refs. This phase is synchronous and cannot be interrupted — once it starts, it runs to completion so the user never sees a half-updated UI [1][4]. The Scheduler: Giving Every Update a Priority Once Fiber made interruptible rendering possible, React needed a way to decide what gets interrupted and what gets priority. That\u0026rsquo;s the job of the scheduler — a separate package that React ships alongside its core [14].\nThe scheduler defines priority levels, each with an internal timeout that determines how long a piece of work can be deferred before React forces it through [14]:\nPriority Timeout Example use case Immediate -1ms (now) Synchronous, critical updates User-blocking 250ms Typing, clicking, dragging Normal 5,000ms Data fetch results, non-urgent updates Low 10,000ms Analytics, background sync Idle effectively never Offscreen content, hidden tabs In React 18, this priority system got even more granular with lanes — a bitmask-based model with up to 31 separate priority \u0026ldquo;lanes\u0026rdquo; that updates can be assigned to [9]. The practical upshot: if you\u0026rsquo;re in the middle of rendering a big, low-priority update (say, filtering a huge list) and the user clicks a button, React can pause the low-priority render, handle the click immediately, and come back to the filtering work afterward [9]. Before Fiber, that simply wasn\u0026rsquo;t architecturally possible — once React started rendering, it couldn\u0026rsquo;t stop.\nThis is also the foundation for useTransition and useDeferredValue, which we\u0026rsquo;ll get to shortly. They\u0026rsquo;re essentially developer-facing levers for \u0026ldquo;please treat this particular update as low priority\u0026rdquo; [10].\nBatching: The Quiet Optimization 10 Years in the Making Okay, here\u0026rsquo;s where it gets genuinely interesting — and where most articles either oversimplify or skip the history entirely. Batching is React\u0026rsquo;s strategy of grouping multiple state updates together so they trigger one re-render instead of many. It sounds simple. The implementation, though, has quietly evolved across nearly every major React release.\nBefore React 18: Batching Only Worked Inside Event Handlers For years, React\u0026rsquo;s batching was tied to its synthetic event system. If you called multiple state setters inside a React event handler (onClick, onChange, etc.), React batched them into a single re-render. But step outside that context — into a setTimeout, a Promise.then(), or a raw addEventListener — and batching stopped working entirely [1][7].\n// React 17 and earlier function handleClick() { setTimeout(() =\u0026gt; { setCount(c =\u0026gt; c + 1); // triggers a re-render setFlag(f =\u0026gt; !f); // triggers ANOTHER re-render }, 1000); } That\u0026rsquo;s two full render-and-commit cycles for what is conceptually one logical update. Not a huge deal for two setState calls, but I\u0026rsquo;ve seen real apps where a single async callback fired six or seven state updates — each one re-rendering a moderately heavy component tree. That adds up.\nThis limitation is why unstable_batchedUpdates existed. It was an undocumented (hence the scary unstable_ prefix) API exported from react-dom that manually wrapped a callback in React\u0026rsquo;s batching context [6]:\nimport { unstable_batchedUpdates } from \u0026#39;react-dom\u0026#39;; setTimeout(() =\u0026gt; { unstable_batchedUpdates(() =\u0026gt; { setCount(c =\u0026gt; c + 1); setFlag(f =\u0026gt; !f); }); // now only ONE re-render }, 1000); If you\u0026rsquo;ve ever used Redux, MobX, or Zustand, you\u0026rsquo;ve benefited from this without knowing it — those libraries wrapped their store update notifications in unstable_batchedUpdates specifically to avoid the \u0026ldquo;multiple renders from external state\u0026rdquo; problem [6]. It was a workaround baked into half the ecosystem because the framework itself couldn\u0026rsquo;t do it consistently.\nReact 18: Automatic Batching, Everywhere React 18 (released March 2022) finally fixed this at the root [1]. With the new createRoot API, every state update gets batched by default, no matter where it originates — timeouts, promises, native event listeners, you name it [1][8].\n// React 18+ with createRoot function handleClick() { setTimeout(() =\u0026gt; { setCount(c =\u0026gt; c + 1); setFlag(f =\u0026gt; !f); // Only ONE re-render, automatically }, 1000); } A few important details that trip people up:\nYou have to opt in via createRoot. If your app still calls the legacy ReactDOM.render(), you don\u0026rsquo;t get automatic batching — React keeps the old behavior for backward compatibility [1]. unstable_batchedUpdates becomes a no-op. Since everything is batched automatically now, that old API just\u0026hellip; doesn\u0026rsquo;t do anything special anymore [6][7]. You can still opt out using flushSync from react-dom, for the rare cases where you genuinely need the DOM updated synchronously before the next line of code runs (measuring layout right after a state change, for example): import { flushSync } from \u0026#39;react-dom\u0026#39;; function handleClick() { flushSync(() =\u0026gt; { setCount(c =\u0026gt; c + 1); }); // DOM has already updated here setFlag(f =\u0026gt; !f); // this one batches normally } I\u0026rsquo;ll be honest — in years of writing React, I\u0026rsquo;ve reached for flushSync maybe twice, both times for measuring DOM nodes immediately after a state-driven layout change. It\u0026rsquo;s a real escape hatch, but if you\u0026rsquo;re using it often, that\u0026rsquo;s usually a sign something else in your architecture needs rethinking.\nReact 18\u0026rsquo;s Concurrent Features: Transitions and Deferred Values Automatic batching reduces the number of renders. But React 18 also introduced tools to control the priority of renders — which is a related but distinct optimization [1].\nstartTransition / useTransition — lets you mark a state update as \u0026ldquo;non-urgent.\u0026rdquo; React will render it in the background and can interrupt it if something more urgent (like a keystroke) comes in [1][10]. useDeferredValue — takes a value and gives you back a version that \u0026ldquo;lags behind\u0026rdquo; during urgent renders, similar in spirit to debouncing but without an arbitrary timer [1]. const [query, setQuery] = useState(\u0026#39;\u0026#39;); const [isPending, startTransition] = useTransition(); function handleChange(e) { setQuery(e.target.value); // urgent: keep the input snappy startTransition(() =\u0026gt; { setSearchResults(filterHugeList(e.target.value)); // low priority }); } The difference from batching is subtle but important: batching is about combining updates into fewer renders, while transitions are about deprioritizing a render so it doesn\u0026rsquo;t block more urgent ones [9][10]. Both ultimately exist because Fiber made rendering interruptible in the first place — without that foundational rewrite, neither would be possible.\nReact 19 and 19.2: Actions, the Compiler, and SSR Batching React 19 (December 2024) didn\u0026rsquo;t overhaul batching the way React 18 did, but it kept building on the same foundation [11]:\nActions API (useActionState, useFormStatus, useOptimistic) — these let you pass async functions directly to \u0026lt;form action={...}\u0026gt;. Under the hood, React automatically manages the pending state, batches the resulting updates, and resets the form on success — all without you manually tracking loading flags [11]. const [error, submitAction, isPending] = useActionState( async (previousState, formData) =\u0026gt; { const error = await updateName(formData.get(\u0026#39;name\u0026#39;)); if (error) return error; return null; }, null, ); React Compiler — this one\u0026rsquo;s a bigger deal for rendering performance overall, even if it\u0026rsquo;s not strictly \u0026ldquo;batching.\u0026rdquo; It reached version 1.0 in October 2025 [13]. The compiler runs at build time and automatically inserts memoization — the kind of optimization you used to hand-write with useMemo, useCallback, and React.memo — so components skip re-rendering when their inputs haven\u0026rsquo;t actually changed [13]. It\u0026rsquo;s been running in production at Meta, and the React team has partnered with Vite, Next.js, and Expo so new projects can turn it on by default [13]. Honestly, this feels like the natural endpoint of everything Fiber set up: first make rendering interruptible and schedulable, then make it batch efficiently, and finally — just skip unnecessary work entirely.\nReact 19.2 (October 2025) extended batching into server rendering too. During streaming SSR, Suspense boundary reveals now get batched for a short window, so multiple boundaries that resolve close together can be sent to the client together instead of trickling in one at a time — which also better matches how the client behaves [12]. This release also shipped the new \u0026lt;Activity /\u0026gt; component (for hiding/restoring UI while preserving state), useEffectEvent, and \u0026ldquo;Performance Tracks\u0026rdquo; — a Chrome DevTools integration that visualizes the scheduler\u0026rsquo;s priority decisions directly in the Performance panel [12].\nPutting It All Together: The Evolution at a Glance Version Year What changed for rendering/batching ≤ React 15 — Stack reconciler — synchronous, non-interruptible recursion [3][4] React 16 2017 Fiber rewrite — interruptible render phase, double-buffered tree, priority-aware scheduling [2][3] React 16–17 2017–2020 Batching only inside React event handlers; unstable_batchedUpdates as a manual workaround [6][7] React 18 2022 Automatic batching everywhere (via createRoot), flushSync opt-out, lanes-based concurrent rendering, useTransition/useDeferredValue [1][9] React 19 2024 Actions API auto-manages pending/optimistic state around async updates [11] React 19.2 2025 SSR batches Suspense boundary reveals; \u0026lt;Activity /\u0026gt;, Performance Tracks for visualizing scheduler priorities [12] React Compiler 1.0 2025 Build-time automatic memoization — skips re-renders without manual useMemo/useCallback [13] Why Any of This Should Matter to You If you\u0026rsquo;re just building forms and dashboards, you might be thinking \u0026ldquo;okay, but I\u0026rsquo;ve never needed flushSync or unstable_batchedUpdates, so why does this matter?\u0026rdquo;\nFair point. But here\u0026rsquo;s where it actually bites people in practice:\nDebugging \u0026ldquo;why did this render twice?\u0026rdquo; is a rite of passage for every React developer, and the answer is almost always rooted in batching behavior — Strict Mode double-invoking effects in development, or an update happening outside a batched context. Migrating from ReactDOM.render() to createRoot isn\u0026rsquo;t just a version bump — it silently changes how your app batches updates. If your app relied on synchronous re-renders somewhere (intentionally or not), automatic batching can expose those assumptions. Performance debugging makes a lot more sense once you know about lanes and priorities. When you open React DevTools\u0026rsquo; profiler — or now, the Performance Tracks in Chrome DevTools [12] — and see renders getting interrupted or deferred, that\u0026rsquo;s not a bug. That\u0026rsquo;s the scheduler doing exactly what it\u0026rsquo;s designed to do. useTransition and useDeferredValue only make sense once you understand that React\u0026rsquo;s render phase is interruptible. Without Fiber, neither hook could exist — there\u0026rsquo;d be nothing to \u0026ldquo;interrupt.\u0026rdquo; What strikes me most, looking back at this whole arc, is how much of it is invisible by design. You don\u0026rsquo;t write code that says \u0026ldquo;use Fiber.\u0026rdquo; You don\u0026rsquo;t explicitly invoke \u0026ldquo;lanes.\u0026rdquo; The entire point of this decade-long rewrite was to make React feel faster without asking developers to think about scheduling at all — and for the most part, it succeeded. The stuff that used to require careful manual batching (unstable_batchedUpdates, debounced inputs, manual shouldComponentUpdate checks) increasingly just\u0026hellip; works, automatically, release after release.\nMaybe with the React Compiler maturing further, the next \u0026ldquo;decade-long arc\u0026rdquo; is about removing manual memoization the same way React 18 removed manual batching. We\u0026rsquo;ll see.\nSources React v18.0 – React Blog React 16: A look inside a rewrite of our frontend UI library – Meta Engineering React Fiber Architecture – acdlite/react-fiber-architecture (GitHub) Inside Fiber: an in-depth overview of the new reconciliation algorithm in React – AG Grid Blog ReactJS Reconciliation – GeeksforGeeks Do you know unstable_batchedUpdates in React? – DEV Community React 18 adds automatic batching – Saeloun Blog What is Automatic Batching in React 18 – GeeksforGeeks Concurrent Rendering and Lane Prioritization in React 18 – Jim\u0026rsquo;s Blog useTransition – React Reference Documentation React 19 – React Blog React 19.2 – React Blog React Compiler v1.0 – React Blog packages/scheduler/src/forks/Scheduler.js – facebook/react (GitHub) ","permalink":"https://cloudmato.com/posts/how-react-works-fiber-and-batch-rendering-explained/","title":"How React Really Works: Fiber and Batch Rendering Explained"},{"content":"Ever opened a project\u0026rsquo;s CSS and found font sizes set in px, em, rem, %, and vw — all in the same file, sometimes on the same element? Yeah, me too. Font size feels like the most boring property in CSS until you realize there are at least eight different ways to express it, and picking the wrong one quietly breaks accessibility for a chunk of your users without throwing a single error.\nWhy this even needs an article I used to think font-size: 16px; was the whole story. Pick a number, slap px on it, done. It took a support ticket from a user who\u0026rsquo;d cranked up their browser\u0026rsquo;s default font size to 24px — and found my \u0026ldquo;responsive\u0026rdquo; site completely ignored it — for me to realize how wrong that was.\nThe unit you choose for font-size isn\u0026rsquo;t just a styling decision. It\u0026rsquo;s an accessibility decision. Some units respect what the browser and the user have configured. Others don\u0026rsquo;t care at all. Some scale beautifully across screen sizes. Others need three media queries to behave. Let\u0026rsquo;s go through all of them, in the order I\u0026rsquo;d actually reach for them.\nThe absolute units: px and the size keywords nobody uses Pixels — the one everyone starts with font-size: 16px is an absolute length. One CSS pixel is a fixed reference unit — it doesn\u0026rsquo;t change based on the parent element, the viewport, or (this is the important part) the user\u0026rsquo;s browser settings [1].\nh1 { font-size: 32px; } p { font-size: 16px; } This is predictable, which is exactly why it\u0026rsquo;s tempting. What you design is what you get — on your monitor, at default zoom. The problem shows up the moment someone doesn\u0026rsquo;t have your exact setup: a user with low vision who\u0026rsquo;s bumped their browser\u0026rsquo;s \u0026ldquo;default font size\u0026rdquo; from 16px to 24px in settings gets… nothing. 16px stays 16px, because px doesn\u0026rsquo;t listen to that preference [1].\nThe keyword units — medium, x-large, and friends Here\u0026rsquo;s a corner of CSS most developers have never touched. The spec actually defines absolute-size keywords: xx-small, x-small, small, medium, large, x-large, xx-large, and (more recently) xxx-large. These map to an internal table the browser maintains, indexed around the user\u0026rsquo;s default font size (medium) [2].\nThere are also relative-size keywords — smaller and larger — which scale up or down from whatever the parent element computed to, typically by a factor between 1.2 and 1.5 [2].\nsmall { font-size: smaller; } .fine-print { font-size: x-small; } Honestly, I almost never see these used in real codebases. They\u0026rsquo;re a relic of the early web, before rem existed, when there wasn\u0026rsquo;t a clean way to say \u0026ldquo;size this relative to the user\u0026rsquo;s preferences.\u0026rdquo; Worth knowing they exist, mostly so you recognize them when you stumble across some legacy stylesheet from 2009.\nThe relative units that actually respond to something This is where it gets interesting — and where the real misconceptions live.\nem — relative to the parent\u0026rsquo;s font size em means \u0026ldquo;relative to the font-size of this element\u0026rsquo;s computed value, which usually means the parent\u0026rsquo;s font-size\u0026rdquo; [3]. So font-size: 1.5em on a child means \u0026ldquo;1.5 times whatever my parent\u0026rsquo;s font-size resolved to.\u0026rdquo;\nSounds harmless. Here\u0026rsquo;s where it gets tricky: if every nested level uses em, the sizes compound.\nhtml { font-size: 16px; } /* root = 16px */ section { font-size: 1.25em; } /* 1.25 × 16px = 20px */ article { font-size: 1.25em; } /* 1.25 × 20px = 25px ← compounding! */ p { font-size: 1.25em; } /* 1.25 × 25px = 31.25px */ That \u0026lt;p\u0026gt; is now rendering at over 31px, when you probably wanted something close to 20px. Nobody intends this — it just happens because of how nesting works, and it\u0026rsquo;s the single most common reason people swear off em for font-size entirely.\nThat said, em isn\u0026rsquo;t useless — it\u0026rsquo;s actually great for things that should scale with their own element\u0026rsquo;s font size, like padding, line-height, or letter-spacing on a button. If the button text gets bigger, you probably want the padding to grow proportionally too. Just don\u0026rsquo;t chain em-based font sizes through five levels of nesting and expect sanity.\nrem — relative to the root, and the closest thing to a default recommendation rem stands for \u0026ldquo;root em.\u0026rdquo; It\u0026rsquo;s always relative to the font-size of the \u0026lt;html\u0026gt; element, no matter how deeply nested the element is [3]. Rewriting that earlier example:\nhtml { font-size: 16px; } /* 1rem = 16px, always */ section { font-size: 1.25rem; } /* 20px */ article { font-size: 1.25rem; } /* 20px — no compounding */ p { font-size: 1.25rem; } /* 20px */ Every 1.25rem means 20px, no matter where it sits in the DOM. This predictability is exactly why rem has become the de-facto recommendation for font sizes, spacing, and most layout dimensions [4].\nBut the real reason rem matters goes beyond predictability — it\u0026rsquo;s about respecting the user. If someone sets their browser\u0026rsquo;s default font size to 20px instead of 16px, every rem-based value on your site scales proportionally, automatically, with zero extra code [5]. With px, that user\u0026rsquo;s preference is just\u0026hellip; ignored.\nPercentages — em\u0026rsquo;s quieter cousin font-size: 80% works almost identically to em — it\u0026rsquo;s relative to the parent\u0026rsquo;s computed font-size, and it compounds the same way through nested elements [3]. You\u0026rsquo;ll see it most often on html { font-size: 62.5%; } tricks (to make 1rem = 10px for easier mental math), or in older stylesheets that predate rem having solid browser support.\nsmaller and larger — the forgotten relative keywords Already mentioned above, but worth repeating here: smaller and larger are relative-size keywords that nudge the font size up or down from the parent\u0026rsquo;s computed size, by roughly 1.2×–1.5× depending on context [2]. Useful occasionally for \u0026lt;small\u0026gt; or \u0026lt;sup\u0026gt;-style elements, but rare in modern component-based CSS where you\u0026rsquo;d more likely use a design token.\nViewport units — sizing text off the screen itself vw (viewport width), vh (viewport height), vmin, and vmax size things relative to the browser\u0026rsquo;s viewport dimensions. 1vw = 1% of the viewport\u0026rsquo;s width. So:\nh1 { font-size: 5vw; } On a 1000px-wide viewport, that\u0026rsquo;s a 50px heading. On a 1920px viewport, it\u0026rsquo;s 96px. It scales continuously with screen size — no breakpoints needed.\nSounds great, right? Here\u0026rsquo;s the catch: viewport units alone are dangerous for font-size. On a tiny phone screen, 5vw might shrink your body text down to something unreadable. On an ultrawide monitor, it might balloon a paragraph into 80px text. And there\u0026rsquo;s a sneakier issue — text sized purely in vw doesn\u0026rsquo;t respond properly to a user\u0026rsquo;s font-size zoom preference on some setups, because the viewport dimensions don\u0026rsquo;t change when text-only zoom is applied [6].\nSo viewport units on their own are basically never the right call for body text. They\u0026rsquo;re a building block for the next thing.\nclamp() — the function that actually makes viewport units usable clamp(min, preferred, max) is, hands down, the unit/function combo I reach for most now on any new project. It takes three values — a minimum, a preferred (often viewport-based) value, and a maximum — and picks whichever keeps you between the floor and ceiling [7].\nh1 { font-size: clamp(2rem, 1.5rem + 3vw, 4rem); } Read that as: \u0026ldquo;Never go below 2rem (32px). Never go above 4rem (64px). In between, scale fluidly based on the viewport width.\u0026rdquo; One line replaces what used to take four or five media queries [8].\nA few practical notes I\u0026rsquo;ve picked up using clamp() across projects:\nCombine rem for the min/max with vw for the preferred value. This keeps your floor and ceiling tied to the user\u0026rsquo;s font-size preference while letting the middle scale fluidly [8]. clamp() is a tool, not a magic fix — if your minimum is too small, low-vision users still can\u0026rsquo;t read it, and if your maximum is too small, it doesn\u0026rsquo;t help users who zoom in for a reason [7]. There\u0026rsquo;s also min() and max() individually, for when you only care about one bound. font-size: min(8vw, 3rem) caps a heading at 3rem but lets it shrink on small screens. ch, ex, and the units that care about the text itself Two oddballs worth knowing:\nch is based on the width of the \u0026ldquo;0\u0026rdquo; character in the current font. It\u0026rsquo;s not really used for font-size, but it\u0026rsquo;s the perfect companion to it — max-width: 66ch keeps a paragraph at roughly 50–75 characters per line, which is the widely cited sweet spot for readability [9]. ex is based on the x-height of the font (roughly the height of a lowercase \u0026ldquo;x\u0026rdquo;). It\u0026rsquo;s rarely supported consistently enough to rely on, but you\u0026rsquo;ll occasionally see it in typography-heavy designs. article p { font-size: clamp(1rem, 0.95rem + 0.3vw, 1.125rem); line-height: 1.6; max-width: 66ch; } That combo — fluid font size, generous line-height, and a ch-based max-width — is basically my go-to for any long-form text block now. Line-height around 1.5–1.6 is the commonly recommended baseline, going higher (1.6–1.7) for lines over 75 characters [9].\nContainer query units — sizing based on the component\u0026rsquo;s box, not the screen This one\u0026rsquo;s newer, and I\u0026rsquo;ll admit I only started using it in the last year or so. Container query units (cqw, cqh, cqi, cqb, cqmin, cqmax) work exactly like viewport units, except they\u0026rsquo;re relative to a containing element you\u0026rsquo;ve opted into, instead of the whole browser viewport [10].\n.card { container-type: inline-size; } .card h2 { font-size: clamp(1rem, 4cqi, 1.5rem); } Why does this matter? Because the same \u0026lt;h2\u0026gt; might live inside a full-width hero section or a narrow sidebar widget. Viewport units can\u0026rsquo;t tell the difference — they only know about the browser window. Container query units let that heading size itself based on the actual space it has, which is huge for component-driven design systems [11].\ncqw = 1% of the query container\u0026rsquo;s width cqi = 1% of the container\u0026rsquo;s inline size (writing-mode aware — generally the one to prefer over cqw) [10] cqb = 1% of the block size cqmin / cqmax = the smaller/larger of the inline and block sizes As with viewport units, wrap these in clamp() so a component squeezed into a tiny sidebar doesn\u0026rsquo;t end up with 4px text [10]. Browser support is solid in evergreen browsers via the CSS containment spec, but I\u0026rsquo;d still test on whatever your analytics say your users are actually running.\nThe rem-vs-px fight — my actual take I\u0026rsquo;ve read a lot of opinions on this, and here\u0026rsquo;s where I\u0026rsquo;ve landed.\nBrowser zoom and the \u0026ldquo;default font size\u0026rdquo; setting are two different things, and people conflate them constantly [6]:\nMechanism What it does Effect on px Effect on rem Browser zoom (Ctrl/Cmd + / -) Scales the entire page proportionally — layout, images, everything Scales up equally Scales up equally \u0026ldquo;Default font size\u0026rdquo; preference Changes what 1rem (and the medium keyword) resolves to No effect Scales proportionally So if a user zooms the whole page to 150%, a 16px element and a 1rem element both end up 50% bigger — no difference there [6]. But if a user goes into their browser settings and changes the default font size from 16px to 24px (a setting many low-vision users rely on permanently, without zooming anything), rem-based sizes scale with that change and px-based sizes don\u0026rsquo;t move at all [4][5].\nThat\u0026rsquo;s the whole argument, really. It\u0026rsquo;s not that px is \u0026ldquo;wrong\u0026rdquo; — it\u0026rsquo;s that px silently opts your text out of one specific accessibility preference, and most developers have no idea that preference exists because they\u0026rsquo;ve never needed to use it.\nMy practical rule, which lines up with what most of the accessibility-focused writers I trust land on:\nUse rem for font sizes, padding, margins, and anything text-related — it respects user preferences [4][5]. Use px for things that should stay visually crisp regardless of text scaling — 1px borders, box-shadow offsets, outline widths. A 1px border that suddenly becomes 1.5px because someone bumped their font size would just look broken. Set html { font-size: 100%; } (or don\u0026rsquo;t touch it at all) rather than hardcoding html { font-size: 16px; } — the latter actually overrides the user\u0026rsquo;s browser default, which defeats the whole point. The 16px gotcha that has nothing to do with design Here\u0026rsquo;s a fun one that bit me on a mobile-first project. We had a slick, compact login form — input fields styled at 14px because the design called for a tight, minimal look. On desktop, perfect. On iPhones, every time someone tapped an input field, Safari would yank the entire page into a zoomed-in view, like it was trying to \u0026ldquo;help.\u0026rdquo;\nTurns out this is a deliberate iOS Safari accessibility behavior: if an input field\u0026rsquo;s computed font-size is below 16px, Safari assumes it\u0026rsquo;s too small to read comfortably while typing and auto-zooms the viewport when that field gets focus [12][13]. It\u0026rsquo;s been this way for years, and it\u0026rsquo;s iOS-specific — Android browsers don\u0026rsquo;t do this [12].\nThe fix was almost embarrassingly simple:\ninput, textarea, select { font-size: 16px; /* or 1rem, assuming root is 16px */ } That\u0026rsquo;s it. Once every form field rendered at 16px or larger, the auto-zoom stopped completely. The catch is that the threshold checks the computed, rendered size — so if you\u0026rsquo;re applying transform: scale() to a form for some reason, that affects whether the zoom kicks in too [13]. This is one of those things that has nothing to do with your visual design preferences and everything to do with a platform quirk — and it\u0026rsquo;s exactly the kind of bug that\u0026rsquo;s invisible until you test on a real device.\nBeyond the browser — how other platforms think about this If you build for more than just web, font-size units look a little different elsewhere, but the underlying tension — fixed vs. user-scalable — is the same everywhere:\nAndroid has dp (density-independent pixels) and sp (scale-independent pixels). They\u0026rsquo;re identical by default, but sp scales when the user changes their system font-size preference, and dp doesn\u0026rsquo;t [14][15]. Android\u0026rsquo;s own accessibility guidance is blunt about it: text sizes should be in sp, full stop, or the system-wide \u0026ldquo;make text bigger\u0026rdquo; setting silently does nothing for your screens [14]. iOS leans on Dynamic Type, where text styles (like .body or .headline) are semantic categories that scale together based on the user\u0026rsquo;s chosen text size — closer in spirit to using rem with semantic class names than to hardcoded points. Design systems and frameworks generally bake rem in as the default. Tailwind CSS, for example, ships its entire type scale in rem, with text-base mapping to font-size: 1rem; line-height: 1.5rem, which works out to 16px/24px at the default root size [16]. If you\u0026rsquo;ve ever wondered why Tailwind\u0026rsquo;s spacing numbers look like fractions of 16, that\u0026rsquo;s why. The pattern across every platform is the same: there\u0026rsquo;s an absolute unit (px, dp, pt) and a scalable unit (rem, sp, Dynamic Type), and the scalable one is the one that respects what the user asked for.\nWhat I actually do now After going back and forth on this across a few projects, here\u0026rsquo;s the setup I default to:\nLeave the root font-size alone (100% / browser default), don\u0026rsquo;t hardcode it to 16px. Base font sizes and spacing tokens in rem, defined once as CSS custom properties — --font-size-base: 1rem; --font-size-lg: 1.25rem; — so the whole scale is adjustable from one place. For hero headings and anything that needs to scale across a huge range of screen sizes, reach for clamp() with rem bounds and a vw-based middle. For components that get reused in wildly different layout contexts (cards, sidebars, widgets), use container query units (cqi) wrapped in clamp(). Pair body text with max-width: 66ch and line-height: 1.5–1.6 for readability. Keep all form inputs at 1rem/16px minimum, no exceptions — that one line has saved me from the iOS zoom bug more than once. Reserve raw px for borders, shadows, and other purely decorative details that shouldn\u0026rsquo;t move when text scales. That\u0026rsquo;s a lot of units for something that used to be \u0026ldquo;just pick a number.\u0026rdquo; But once you\u0026rsquo;ve seen a site become unusable for someone who simply changed a browser setting most developers don\u0026rsquo;t even know exists, \u0026ldquo;just pick a number\u0026rdquo; stops feeling like enough.\nSources font-size - CSS: Cascading Style Sheets | MDN CSS type - MDN Web Docs CSS Units Guide - rem, em, px, vh, vw Explained | Saeloun Blog REM? PX? Why not both? Accessibility: px or rem? Totally remdom, or How browsers zoom text - Manuel Matuzovic Linearly Scale font-size with CSS clamp() Based on the Viewport | CSS-Tricks Modern Fluid Typography Using CSS Clamp — Smashing Magazine Optimal Line Length for Readability: The 50–75 Character Rule Explained | UXPin CSS Container Query Units CSS container queries - MDN Web Docs 16px or Larger Text Prevents iOS Form Zoom | CSS-Tricks Defensive CSS - Input zoom on iOS Safari Text scaling - Android Accessibility Help dp and sp in Android UI Design Font Size - Tailwind CSS ","permalink":"https://cloudmato.com/posts/every-way-to-set-font-size-in-ui/","title":"Every Way to Set Font Size in a UI (and Which Wins)"},{"content":"Every January some report says \u0026ldquo;this is the year AI changes everything,\u0026rdquo; and every year I roll my eyes a little. But sitting here in mid-2026, I genuinely can\u0026rsquo;t roll my eyes anymore. Browsers are agents now. Chips are shipping \u0026ldquo;physical AI\u0026rdquo; models. Robots are clocking actual factory shifts. Some of this is real progress, some of it is still marketing dressed up as a product launch — and honestly, figuring out which is which is half the fun.\nI\u0026rsquo;ve spent the last few weeks going through what\u0026rsquo;s actually shipped versus what\u0026rsquo;s still vaporware, and I want to walk through it the way I\u0026rsquo;d explain it to a friend over chai — what\u0026rsquo;s real, what\u0026rsquo;s hype, and what you should actually care about.\nAI Agents Finally Started Doing Real Work (Not Just Demos) For the last couple of years, \u0026ldquo;AI agent\u0026rdquo; mostly meant a chatbot with extra steps. That\u0026rsquo;s changed. Gartner predicts that 40% of enterprise applications will embed AI agents by the end of 2026, up from less than 5% in 2025 [1]. That\u0026rsquo;s not a small jump — that\u0026rsquo;s basically every SaaS tool you use at work quietly getting an \u0026ldquo;agent\u0026rdquo; tab.\nWhat\u0026rsquo;s more interesting to me is the shift from single do-everything assistants to teams of specialized agents working together. Gartner reported a 1,445% surge in multi-agent system inquiries between Q1 2024 and Q2 2025 [2]. Think of it like hiring — instead of one generalist employee, companies are now spinning up a \u0026ldquo;team\u0026rdquo;: one agent that plans, one that writes code, one that checks the output, one that files the report. They argue with each other, basically, until the task is done.\nGoogle Cloud\u0026rsquo;s own research backs this up — agentic AI is expected to reshape sales, software development, finance, healthcare, logistics, cybersecurity, and support, moving from \u0026ldquo;pilot project\u0026rdquo; to \u0026ldquo;thing the business actually depends on\u0026rdquo; [3]. I\u0026rsquo;ve started wiring up small agent workflows for repetitive parts of my own job — log triage, PR summaries, that kind of thing — and the honest truth is: when it works, it\u0026rsquo;s genuinely useful. When it doesn\u0026rsquo;t, it fails in weirdly confident ways. Trust it, but watch it.\nThe Frontier Model Pile-Up: GPT-5.5, Gemini 3.1 Pro, Claude Opus 4.8, Grok 4.3 If you feel like you can\u0026rsquo;t keep up with model names anymore — you\u0026rsquo;re not imagining it. By June 2026, the model everyone was talking about a few months ago has already been replaced. GPT-5.1 became GPT-5.5, Claude Opus 4.5 became Opus 4.8, and Gemini 3 turned into Gemini 3.1 Pro [4].\nHere\u0026rsquo;s roughly how the big four stack up right now, based on the latest benchmark roundups [4][5]:\nModel Maker Best known for Notable trait Claude Opus 4.8 Anthropic Coding, leads overall intelligence index Edges out GPT-5.5 on coding tasks GPT-5.5 OpenAI Creative writing, coding Neck-and-neck with Opus on code Gemini 3.1 Pro Google Reasoning, data analysis Strong multimodal + deep Chrome integration Grok 4.3 xAI Agentic/tool-use tasks Cheapest of the four, strong on tools Claude Opus 4.8 currently tops the Artificial Analysis Intelligence Index at 61.4, just ahead of GPT-5.5 at 60.2 [4]. But honestly? These gaps are small enough that the \u0026ldquo;best\u0026rdquo; model is really the one that fits your workflow and budget, not whichever one is technically winning a leaderboard this week. By the time you read this, there\u0026rsquo;s a decent chance one of these numbers has already shifted again.\nYour Browser Is Quietly Becoming an Agent This is the one that snuck up on me. Three major players are now turning the humble web browser into something that acts on your behalf: OpenAI\u0026rsquo;s ChatGPT Atlas, Perplexity\u0026rsquo;s Comet, and Google\u0026rsquo;s Gemini in Chrome [6][7].\nChatGPT Atlas — launched as a dedicated browser with \u0026ldquo;Agent Mode,\u0026rdquo; which can execute multi-step tasks autonomously, plus a memory feature so you can ask \u0026ldquo;find all the job postings I was looking at last week\u0026rdquo; and actually get an answer [6]. Gemini in Chrome (Auto Browse) — rolled out to all Chrome users in January 2026, meaning roughly 3 billion Chrome users now technically have access to autonomous browsing [7]. Perplexity Comet — already crossed a million users, expanded to enterprise customers in March 2026, and can be silently deployed across company devices via MDM [7]. Atlas reportedly wins on memory depth and agent autonomy, Comet wins on speed and price for quick lookups [7]. My honest take: this is the part of 2026 tech that\u0026rsquo;s going to mess with website analytics and SEO the most. If an AI is \u0026ldquo;browsing\u0026rdquo; on your behalf and summarizing pages instead of you clicking through, a whole industry built around page views just got a rug pull. Nobody fully knows how this settles yet.\nNvidia\u0026rsquo;s Vera Rubin: The Chips Powering All of This None of the above happens without silicon, and Nvidia spent CES 2026 making sure everyone knew exactly whose silicon. The Vera Rubin platform is now in full production — six new chips designed to deliver up to a 10x reduction in inference token cost compared to the previous Blackwell generation [8].\nThe setup combines one Vera CPU with two Rubin GPUs in a single processor, plus four supporting chips: the NVLink 6 Switch, ConnectX-9 SuperNIC, BlueField-4 DPU, and Spectrum-6 Ethernet Switch [9]. In practical terms, Nvidia says an NVL72 rack can train models using a quarter of the GPUs the old Blackwell setup needed, while delivering 10x higher inference throughput per watt at a tenth of the cost per token [9].\nAWS, Google Cloud, Microsoft, and Oracle Cloud are among the first to deploy Rubin-based instances later in 2026 [10]. If that all sounds abstract, here\u0026rsquo;s the translation: the AI services you use should get noticeably cheaper to run by next year — whether that savings gets passed to you or just pocketed as margin is, as always, a different question entirely.\nPhysical AI: Humanoid Robots Leave the Lab for the Factory Floor Who\u0026rsquo;s actually winning? This is the category where 2026 stopped being theoretical. Tesla\u0026rsquo;s Optimus, Figure\u0026rsquo;s Figure 03, and Apptronik\u0026rsquo;s Apollo are all now shipping units to industrial pilot customers — the first time three humanoid robot programs have hit early production simultaneously [10].\nRobot Maker Real-world traction Target price Figure 03 Figure AI Helped build 30,000+ BMW X3s at Plant Spartanburg; expanding to Plant Leipzig Not publicly listed Optimus (Gen 3) Tesla Zero announced external customers as of April 2026 ~$20,000–$30,000 [11] Apollo Apptronik Shipping to industrial pilot customers Q2 2026 Not publicly listed According to one tracker, Figure currently scores 78.9 out of 100 on overall readiness versus Tesla\u0026rsquo;s 45.1, with the biggest gap in real-world deployment (79 vs 32) [10]. That\u0026rsquo;s a pretty blunt scoreboard for a company as hyped as Tesla — and it lines up with what I\u0026rsquo;d expect. Building a humanoid robot is \u0026ldquo;easy\u0026rdquo; compared to getting a factory to actually trust it on the line for ten months straight. Figure did that. Tesla, as of this writing, hasn\u0026rsquo;t shown it publicly yet.\nThe world models behind the robots What\u0026rsquo;s quietly enabling all of this is Nvidia\u0026rsquo;s Cosmos 3, an open \u0026ldquo;world model\u0026rdquo; trained on 20 trillion tokens of multimodal data — nearly a billion images, 400 million videos, plus audio and action data from humans and robots [12]. The idea is that robots can practice in a simulated world before ever touching a real part, which is exactly the kind of unglamorous infrastructure work that makes the flashy robot demos possible in the first place [13].\nSmart Glasses: The Wearable War Everyone Saw Coming Meta has had the smart glasses market basically to itself — it sold over 7 million Ray-Ban smart glasses in 2025 and controls roughly 82% of the global market [14]. Its $800 Ray-Ban Display, with an actual screen built into the lens, has been sitting there unanswered for months.\nApple\u0026rsquo;s response is reportedly coming. Bloomberg\u0026rsquo;s Mark Gurman says Apple is testing four distinct frame designs for its first smart glasses, codenamed N50, using acetate for a \u0026ldquo;higher-end\u0026rdquo; feel compared to the typical plastic frames [15]. The plan, allegedly, is to unveil them in September or October 2026 — deliberately timed to disrupt Meta\u0026rsquo;s momentum right before the holiday shopping season [14].\nHere\u0026rsquo;s the catch though: Apple\u0026rsquo;s first-gen glasses reportedly won\u0026rsquo;t have a display or any augmented reality features at all. They\u0026rsquo;ll pair with an iPhone over Bluetooth for processing, much like the current Meta Ray-Bans without the Display model [15]. So depending on who you ask, Apple is either playing it safe with a \u0026ldquo;good enough\u0026rdquo; first version, or it\u0026rsquo;s already a generation behind on the feature that actually matters. I\u0026rsquo;d bet on the former — Apple rarely ships the flashy version first, it ships the \u0026ldquo;boring but reliable\u0026rdquo; version and lets the ecosystem catch up.\nFoldable Phones Got a Third Fold — And a Reality Check Tri-fold phones had their big \u0026ldquo;arrived\u0026rdquo; moment at CES 2026, and then almost immediately had a humbling moment too.\nDevice Maker Unfolded display Status (mid-2026) Galaxy Z TriFold Samsung 10-inch — largest screen ever on a Galaxy phone Sales paused in Korea \u0026amp; US after ~3 months [16][17] Mate XT Huawei 6.4\u0026quot; phone expands to 10.2\u0026quot; 3K display via Z-fold Still on sale, early mover [18] Tri-fold (unannounced) Xiaomi TBD In development [18] Samsung\u0026rsquo;s Galaxy Z TriFold launched in Korea on December 12, 2025, and the US on January 30, 2026 — folded, it\u0026rsquo;s 12.9mm thick and weighs about 10.9 ounces [16]. Then, in March 2026, Samsung quietly announced it was ending Z TriFold sales in both Korea and the US after just three months, while reportedly working on a second-gen version for 2027 [17].\nThat\u0026rsquo;s a pretty fast reversal for a flagship device. The problem, according to reviewers, wasn\u0026rsquo;t really the hardware — it\u0026rsquo;s that the software hasn\u0026rsquo;t caught up. A lot of apps just do an \u0026ldquo;equal proportion zoom\u0026rdquo; on the bigger screen instead of actually using the extra space, so you\u0026rsquo;re left holding a 10-inch tablet that behaves like a stretched phone [18]. This is the classic foldable problem all over again — manufacturers keep solving the hinge before they solve the experience.\nQuantum Computing Enters Its \u0026ldquo;Fault-Tolerant Era\u0026rdquo; Quantum computing has spent years being \u0026ldquo;five years away,\u0026rdquo; and 2026 — officially designated the International Year of Quantum Science and Technology by the UN — is the year a few of those five-year promises actually started landing [19].\nFault tolerance breakthroughs: Work out of Mikhail Lukin\u0026rsquo;s lab at Harvard has reportedly pushed quantum error-correction timelines forward faster than expected, attracting billions in private investment [19]. Cryoelectronics for ion traps: Researchers at Fermilab and MIT\u0026rsquo;s Lincoln Laboratory successfully trapped and manipulated ions using in-vacuum cryoelectronics — a real step toward large-scale ion-trap quantum systems [21]. Room-temperature quantum devices: Stanford researchers built a device that uses twisted light to entangle photons and electrons without needing extreme cooling, potentially opening the door to smaller, cheaper quantum systems [20]. The industry framing is that 2026 marks the point where adding more qubits actually starts reducing error rates instead of just adding more noise — which, if you\u0026rsquo;ve followed quantum computing skepticism over the years, is kind of the whole ballgame [19]. I\u0026rsquo;d still file this under \u0026ldquo;exciting, but not in your pocket anytime soon\u0026rdquo; — but the gap between \u0026ldquo;lab curiosity\u0026rdquo; and \u0026ldquo;engineering roadmap\u0026rdquo; genuinely seems to have narrowed this year.\nThe Not-So-Fun Part: AI\u0026rsquo;s Massive Power Bill Here\u0026rsquo;s the thing nobody puts on the conference keynote slide: all of this — the agents, the models, the robots — needs an absurd amount of electricity. Data center electricity consumption could approach 1,050 terawatt-hours by 2026, which would make data centers the fifth-largest electricity consumer in the world, between Japan and Russia [22].\nAs of 2024, most of that power (40%) came from natural gas, with nuclear at around 20% [22]. But that\u0026rsquo;s shifting fast. Meta\u0026rsquo;s deals with Vistra, TerraPower, Oklo, and Constellation Energy make it one of the largest corporate buyers of nuclear power in American history, supporting up to 6.6 GW of clean energy by 2035 [23]. Microsoft is reportedly getting Constellation to restart the Three Mile Island plant by 2027, and AWS signed a 17-year, 1.92 GW power deal tied to the Susquehanna nuclear plant [22].\nThis is the part of the AI story that I think gets the least attention but matters the most long-term. Every \u0026ldquo;AI just got 10x cheaper\u0026rdquo; headline is sitting on top of an energy infrastructure story that\u0026rsquo;s playing out in the background, mostly in places you\u0026rsquo;ll never hear about unless a nuclear plant near you suddenly gets a new corporate tenant.\nBrain-Computer Interfaces Quietly Get Real Neuralink had a big claim this year: Elon Musk says the company will start high-volume production of brain-computer interface devices in 2026, moving to an almost entirely automated surgical procedure where the device threads go through the dura without needing to remove it [24].\nAs of the latest count, three people with paralysis have received implants, with the company having implanted up to 12 patients total by last count [25]. The first recipient, Noland Arbaugh, has used his implant to play video games and online chess [25]. Neuralink\u0026rsquo;s Blindsight implant — aimed at restoring vision for people who are completely blind — is scheduled for its first patient trial in 2026 [25], and the company is also building a surgical robot designed to reach any part of the brain with high precision [25].\nThe honest caveat here: even optimistic reporting puts realistic commercial availability at 2028-2030 [25]. So \u0026ldquo;ramping up production\u0026rdquo; in 2026 mostly means scaling clinical trials, not shipping a consumer product. Still — going from \u0026ldquo;1 patient\u0026rdquo; to \u0026ldquo;12 patients and an automated surgical robot\u0026rdquo; in about 18 months is a real pace of progress, whatever you think of the company behind it.\nCES 2026 Grab Bag: The Smaller Stuff Worth Knowing About Not everything from CES 2026 needs its own section, but a few things are worth filing away:\nSamsung Micro RGB TVs are going mainstream — the 2026 lineup spans 55 to 115 inches, bringing a backlight tech previously reserved for ultra-premium sets down to more normal price tiers [26]. AMD\u0026rsquo;s Ryzen 7 9850X3D bumps clock speeds by 400 MHz over the 9800X3D — a modest but welcome bump if you\u0026rsquo;re due for a gaming PC upgrade [27]. Samsung and LG both showed new RGB-stripe OLED panels, including a 34-inch, 360 Hz QD-OLED from Samsung — good news if competitive gaming is your thing [27]. Amazon\u0026rsquo;s Ember Artline TVs are designed to look like framed pictures on your wall, running Fire TV with Alexa built in, in 55- and 65-inch sizes [28]. Caterpillar\u0026rsquo;s AI Assistant — yes, the heavy machinery company — now helps customers buy, maintain, and operate equipment, which is a good reminder that \u0026ldquo;AI everywhere\u0026rdquo; isn\u0026rsquo;t just a phone and laptop thing anymore [28]. Honestly, half of CES every year is this kind of stuff — incremental, useful, completely unglamorous. But it\u0026rsquo;s also the stuff that actually shows up in your life faster than humanoid robots or smart glasses ever will.\nIf there\u0026rsquo;s one thread running through all of this, it\u0026rsquo;s that 2026 is the year the gap between \u0026ldquo;AI demo\u0026rdquo; and \u0026ldquo;AI in production\u0026rdquo; started closing — in your browser, in a BMW factory, in a hospital implanting brain chips, and in a power plant getting a second life because a data center needs the electricity. Some of it\u0026rsquo;ll age badly (I\u0026rsquo;m looking at you, Galaxy Z TriFold). Most of it won\u0026rsquo;t wait around for you to catch up.\nSources 8 Ways AI Agents Are Evolving in 2026 - Salesforce 7 Agentic AI Trends to Watch in 2026 - MachineLearningMastery.com AI agent trends 2026 report | Google Cloud Gpt-5.1 vs Gemini 3 Pro vs Claude Opus 4.5 Breakdown Report - Vellum Best AI Models 2026: GPT-5 vs Claude 4.5 Opus vs Gemini 3 Pro (Complete Comparison) Introducing ChatGPT Atlas | OpenAI The Agentic Browser Landscape in 2026: A Complete Guide | No Hacks NVIDIA Vera Rubin Opens Agentic AI Frontier | NVIDIA Newsroom Inside the NVIDIA Vera Rubin Platform: Six New Chips, One AI Supercomputer | NVIDIA Technical Blog Figure 03 vs Tesla Optimus Comparison Tracker (2026) – New Market Pitch Tesla robot price in 2026: Everything you need to know about Optimus - Standard Bots NVIDIA Launches Cosmos 3, the Open Frontier Foundation Model for Physical AI | NVIDIA Newsroom Nvidia\u0026rsquo;s Cosmos 3 open AI world model helps robots, autonomous vehicles - Axios Apple May Unveil Smart Glasses in Late 2026 - and Meta Has Every Reason to Worry - Gadget Review Apple reportedly testing out four different styles for its smart glasses that will rival Meta Ray-Bans - Engadget Samsung Galaxy Z TriFold hands-on: Flexing is believing at CES 2026 - Engadget Samsung Galaxy Z TriFold - Wikipedia 2026 Tri-Fold Smartphone Guide: Comparing Huawei Mate XT and Samsung Galaxy Z TriFold - Vertu Harvard Researchers: Quantum Computing Advancing Faster Than Expected - The Quantum Insider Stanford quantum computing breakthrough uses twisted light to work without extreme cooling | ScienceDaily DOE national quantum research centers reach breakthrough towards building scalable quantum computers - Fermilab Data Centres, Artificial Intelligence and Cryptocurrencies Eye Advanced Nuclear to Meet Growing Power Needs | IAEA Meta Announces Nuclear Energy Projects, Unlocking Up to 6.6 GW to Power American Leadership in AI Innovation Neuralink to kick-start \u0026lsquo;high-volume production\u0026rsquo; of brain-computer interface devices, Elon Musk says - Fierce Biotech Neuralink\u0026rsquo;s next robot could reach any part of the brain - YourStory CES 2026: The Future is Here CES 2026: all the upcoming releases and announcements a PC gamer should know about | PC Gamer What Not To Miss at CES® 2026 ","permalink":"https://cloudmato.com/posts/latest-tech-2026/","title":"Latest Tech in 2026: AI Agents, Robots, and Real Hardware"},{"content":"Open up any domain\u0026rsquo;s DNS settings and you\u0026rsquo;ll see a wall of cryptic codes — A, AAAA, MX, TXT, SRV, CAA, SVCB — and it genuinely looks like someone kept bolting random parts onto an old engine. Which, honestly, is exactly what happened. Every single one of these record types exists because the internet hit a wall that the existing records couldn\u0026rsquo;t get past, and once you see that history laid out, the whole mess actually starts to make sense.\nOkay, So What Even Is a DNS Record? Before the timeline, a quick refresher because it matters for everything that follows.\nDNS is the internet\u0026rsquo;s phonebook [1]. Every entry in that phonebook is a \u0026ldquo;resource record,\u0026rdquo; and every record — no matter the type — follows the same basic shape that was nailed down back in November 1987 in RFC 1035 [2]:\nexample.com. 3600 IN A 192.0.2.10 | | | | | name TTL class type data Name, time-to-live, class (almost always IN for \u0026ldquo;internet\u0026rdquo; — nobody uses the others anymore), type, and then data whose format depends entirely on the type. That type field is the whole story here. It\u0026rsquo;s just a number. IANA keeps a registry of which number means what [16], and the data that follows gets interpreted completely differently depending on which number it is.\nSo when people ask \u0026ldquo;why are there so many DNS record types,\u0026rdquo; the honest answer is: because the type field was designed to be extended. It\u0026rsquo;s basically a plug-in system. Every time the internet needed a new kind of thing to look up — a mail server, an IPv6 address, a chat server, a certificate policy — someone wrote a proposal, the IETF turned it into an RFC, and IANA handed out a new type number. Forty-plus years of \u0026ldquo;the internet needed one more thing\u0026rdquo; is why your DNS panel looks like a control room.\nThe Original Crew (1983–1987) DNS itself wasn\u0026rsquo;t always there. Before 1983, the entire internet (then ARPANET) relied on a single text file called HOSTS.TXT, maintained centrally by SRI International, that every machine downloaded to map names to addresses [4]. That worked fine when there were a few hundred hosts. It did not work when the network started growing exponentially.\nPaul Mockapetris, working at USC\u0026rsquo;s Information Sciences Institute under Jon Postel, was asked to fix this. In 1983 he wrote RFC 882 and RFC 883, proposing a distributed, hierarchical database instead of one giant file [3]. Four years later those got revised and replaced by RFC 1034 and RFC 1035 in November 1987 [2], and that\u0026rsquo;s the document that defines the core record types still doing most of the heavy lifting today.\nHere\u0026rsquo;s the original lineup and why each one needed to exist as its own type rather than just being a flag on another record:\nRecord What it does Why it couldn\u0026rsquo;t just be folded into another type A Maps a hostname to an IPv4 address This is the basic \u0026ldquo;phonebook\u0026rdquo; lookup — the whole reason DNS exists NS Says which servers are authoritative for a zone Needed so resolvers know where to even ask the question CNAME Aliases one name to another name Lets you point www at example.com without duplicating records MX Points to the mail server for a domain Mail servers are often different machines than web servers — DNS needed a way to say \u0026ldquo;send mail here, not there\u0026rdquo; SOA Stores zone admin metadata (serial number, refresh/retry timers, admin contact) Secondary DNS servers need this to know when to re-sync from the primary PTR Reverse lookup — IP address to hostname A completely different direction of query than A records, so it needed its own structure TXT Free-form text A catch-all \u0026ldquo;write whatever you want here\u0026rdquo; field — turned out to be wildly useful later (more on that below) HINFO / WKS Host hardware/OS info and well-known services Mostly dead now — a good early example of a record type that just\u0026hellip; didn\u0026rsquo;t survive That last row is actually instructive. Not every record type that gets defined sticks around. HINFO was meant to advertise what CPU and OS a host ran — which sounds like a privacy nightmare today, and indeed almost nobody publishes it anymore. DNS isn\u0026rsquo;t just additive; it occasionally prunes itself.\nHang On — Why Not Just Cram Everything Into One Record? Fair question. Here\u0026rsquo;s why that doesn\u0026rsquo;t work, and it\u0026rsquo;s also the key to understanding the rest of this article.\nEach record type defines its own wire format — the exact binary layout of the data that follows the type field. An MX record\u0026rsquo;s data is \u0026ldquo;a priority number plus a hostname.\u0026rdquo; A CAA record\u0026rsquo;s data is \u0026ldquo;a flag, a tag, and a value.\u0026rdquo; An SRV record\u0026rsquo;s data is \u0026ldquo;priority, weight, port, and target.\u0026rdquo; These shapes are fundamentally different, and a resolver needs to know the shape in advance to parse it correctly.\nThe clever part of the design is that unknown record types are just opaque blobs to old software. A 1990s DNS resolver that\u0026rsquo;s never heard of a CAA record will just shrug and pass it along unparsed instead of crashing. This is the same principle behind graceful degradation that\u0026rsquo;s kept DNS backward-compatible for over 40 years — old clients don\u0026rsquo;t choke on new types, they just ignore what they don\u0026rsquo;t understand [17].\nBut that forward-compatibility cuts both ways. A brand-new record type is technically \u0026ldquo;supported\u0026rdquo; the moment IANA assigns it a number, but it\u0026rsquo;s only useful once registrars, DNS hosting panels, validators, and applications all bother to add explicit support for it. We\u0026rsquo;ll see a great example of a record type that got defined and then mostly abandoned in favor of an older one — keep reading.\nThe Timeline: Every Time DNS Got a New Type, and Why This is the part that actually answers \u0026ldquo;why are there so many\u0026rdquo; — each addition maps to a specific moment where the internet\u0026rsquo;s problems outgrew what existed.\n1995 (then 2003): AAAA — the internet was running out of addresses IPv4 gives you about 4.3 billion addresses. That sounds like a lot until you remember every phone, laptop, smart bulb, and fridge wants one. By the mid-90s, engineers could already see the exhaustion coming, so IPv6 was designed with 128-bit addresses — vastly more room.\nBut RFC 1035\u0026rsquo;s A record was hardcoded for 32-bit IPv4 addresses. There was no way to stuff a 128-bit address into it. So in December 1995, AAAA (\u0026ldquo;quad-A,\u0026rdquo; because an IPv6 address is four times the size of an IPv4 one) was introduced in RFC 1886 [5]. It got revised in October 2003 as RFC 3596, which also moved reverse lookups from the awkward IP6.INT domain to IP6.ARPA [5]. If your website has ever had both an A and an AAAA record pointing at it, that\u0026rsquo;s your server saying \u0026ldquo;reachable over IPv4 and IPv6, take your pick.\u0026rdquo;\n1996/2000: SRV — DNS grows up beyond web and mail Here\u0026rsquo;s something that bugged protocol designers for years: MX records let you say \u0026ldquo;mail for this domain goes to that server, on a different port/host than the website.\u0026rdquo; Great. But what if you\u0026rsquo;re running a chat server, or a directory service, or literally any other protocol? Before SRV, every new protocol had to invent its own MX-style hack, or just hardcode a port and hope for the best.\nSRV records generalized this. First proposed in RFC 2052 (1996) and standardized in RFC 2782 (February 2000), an SRV record specifies a priority, a weight (for load balancing), a port, and a target host [6]. Suddenly any service — XMPP chat, SIP voice calls, Microsoft Active Directory, even Minecraft servers — could say \u0026ldquo;here\u0026rsquo;s exactly which host and port to connect to for this specific service,\u0026rdquo; and admins could move things around or add failover servers without breaking client configs [6].\n2000: NAPTR — when phone numbers needed DNS too Around the same time, the telecom world was trying to merge with the internet — specifically, mapping traditional phone numbers (E.164 format) onto internet services via something called ENUM, and routing SIP (Session Initiation Protocol) calls.\nNAPTR (Naming Authority Pointer) records, defined in RFC 2915 (2000), solved this by mapping a name to a regular-expression-based rewrite rule that produces a new domain name or URI [7]. Combined with SRV records, NAPTR let a single phone number resolve through DNS into \u0026ldquo;here\u0026rsquo;s the SIP server, here\u0026rsquo;s the port, here\u0026rsquo;s the protocol\u0026rdquo; [7]. It\u0026rsquo;s a niche record type for most people, but if you\u0026rsquo;ve ever made a VoIP call, NAPTR was quietly doing work behind the scenes.\n2005: DNSSEC\u0026rsquo;s alphabet soup — DNSKEY, RRSIG, NSEC, DS For its first ~20 years, DNS had zero authentication. A resolver asks a question, gets an answer, and just\u0026hellip; trusts it. That\u0026rsquo;s a massive problem if someone can intercept or forge that answer — known as cache poisoning, where an attacker tricks a resolver into caching a fake IP address for a legitimate domain, silently redirecting traffic to a malicious server.\nDNSSEC (DNS Security Extensions) fixes this with digital signatures, and it needed four new record types, formalized in RFC 4034 (March 2005), which obsoleted an earlier and clunkier 1999 attempt [8]:\nDNSKEY — holds the public key used to verify signatures for a zone RRSIG — the actual digital signature covering a set of records NSEC — proves a record doesn\u0026rsquo;t exist (so attackers can\u0026rsquo;t just claim \u0026ldquo;no such domain\u0026rdquo; undetected) DS — a hash that links a child zone\u0026rsquo;s key up to its parent zone, building a chain of trust Adoption was slow for years — DNSSEC adds real complexity to zone management — but a wave of high-profile cache-poisoning research in the years following gave it a serious push, and DNSSEC is now considered table stakes for any domain that takes security seriously.\n2006–2015: SPF, DKIM, DMARC — the war on email spoofing If you\u0026rsquo;ve ever set up email for a custom domain, you\u0026rsquo;ve run into this trio, and honestly this is where DNS got really messy — because all three of these technically live inside the humble TXT record, the one that was originally just \u0026ldquo;write a free-text note here.\u0026rdquo;\nSPF (Sender Policy Framework) lists which mail servers are allowed to send email as your domain. It started in RFC 4408 (April 2006) as an IETF experiment [9], and — here\u0026rsquo;s the genuinely weird part — it originally got its own dedicated record type, type 99, literally called \u0026ldquo;SPF.\u0026rdquo; Nobody used it. DNS software vendors and admins just kept publishing SPF policy as TXT records because that\u0026rsquo;s what already worked everywhere. By RFC 7208 (April 2014), the IETF gave up and formally deprecated the dedicated SPF record type, mandating TXT-only [9][10]. It\u0026rsquo;s a rare case of a brand-new record type being defined, going almost completely unused, and then officially killed off less than a decade later [10]. DKIM (DomainKeys Identified Mail), standardized as RFC 6376 in 2011 after about six years of development, adds a cryptographic signature to outgoing emails so receivers can verify the message wasn\u0026rsquo;t tampered with — published as a TXT record at selector._domainkey.yourdomain.com. DMARC (Domain-based Message Authentication, Reporting and Conformance) ties SPF and DKIM together and tells receiving servers what to do if a message fails both — quarantine it, reject it, or just report on it. It came out of an industry consortium (Google, Microsoft, Yahoo, PayPal, and others under DMARC.org) and was published as RFC 7489 in March 2015 [11], living at _dmarc.yourdomain.com. Why three separate things instead of one record type? Because they solve three different trust problems — who\u0026rsquo;s allowed to send (SPF), was this message altered (DKIM), and what should happen if checks fail, plus give me visibility (DMARC) — and they were built by different groups at different times, which is honestly very on-brand for how DNS evolves.\n2012–2013: TLSA and CAA — trust issues with certificate authorities Around 2011–2012, the web PKI system — the certificate authorities (CAs) that issue the padlock-icon certificates for HTTPS — had a rough few years. Several CAs suffered serious breaches that allowed attackers to get valid certificates issued for domains they didn\u0026rsquo;t own [12]. The fundamental issue: any of the hundreds of trusted CAs in your browser can issue a certificate for any domain, with no way for the domain owner to say \u0026ldquo;only THIS CA is allowed to do that.\u0026rdquo;\nTwo record types addressed this from different angles:\nTLSA, defined in RFC 6698 (August 2012) as part of DANE (DNS-based Authentication of Named Entities), lets a domain pin the exact certificate or CA it expects, with DNSSEC providing the trust anchor — bypassing the traditional CA system entirely [12]. In practice, adoption has mostly been limited to SMTP mail servers and some XMPP deployments [12]. CAA, first drafted in October 2010 by Phillip Hallam-Baker and Rob Stradling and published as RFC 6844 in January 2013, takes a lighter-touch approach: it just lets a domain owner publish a simple policy saying \u0026ldquo;only these specific CAs may issue certificates for me\u0026rdquo; [13][14]. The CA/Browser Forum made checking CAA records mandatory for all public CAs starting September 8, 2017 [14], and the spec was later refined as RFC 8659 in November 2019. If you\u0026rsquo;ve ever set up a CAA record to lock your domain to Let\u0026rsquo;s Encrypt or DigiCert, this is why it exists [15]. 2023: HTTPS and SVCB — DNS gets smarter about connections This is the newest entry, and it\u0026rsquo;s a good one to end on because it shows the pattern is still very much alive.\nFor most of the web\u0026rsquo;s history, visiting a site meant: DNS lookup for an A/AAAA record → open a TCP connection → do a TLS handshake → then send the HTTP request. Each step happens somewhat blindly — your browser doesn\u0026rsquo;t know in advance if the server supports HTTP/3, has alternate endpoints, or needs special TLS configuration.\nSVCB (Service Binding) and HTTPS records, standardized together in RFC 9460 (November 2023), let a single DNS answer carry connection hints up front — preferred protocols like HTTP/3, alternate ports, IP hints, and parameters for encrypted ClientHello [the privacy-protecting TLS extension] [16]. The HTTPS record type is essentially a specialized version of SVCB just for web traffic, and it can do something CNAME never could: alias an apex domain (like example.com itself, not just www) to another name [16]. Browsers and CDNs are still rolling out support, but it\u0026rsquo;s the clearest sign yet that DNS keeps adapting as the web\u0026rsquo;s underlying protocols change.\nThe Cheat Sheet: Which Record Do You Actually Need? If you\u0026rsquo;re staring at a DNS panel right now trying to figure out what to actually configure, here\u0026rsquo;s the practical mapping:\nWhat you\u0026rsquo;re trying to do Record type(s) you need Point your domain at a server (IPv4) A Point your domain at a server (IPv6) AAAA Alias one hostname to another (e.g., www → example.com) CNAME Route incoming email to your mail provider MX Authorize servers to send mail as your domain TXT (SPF policy) Sign outgoing email cryptographically TXT (DKIM key, at selector._domainkey) Set a policy for failed email auth checks TXT (DMARC, at _dmarc) Prove domain ownership to Google/AWS/etc. TXT (verification token) Run a non-web/non-mail service (chat, AD, game server) SRV Restrict which CAs can issue TLS certs for you CAA Pin an exact TLS certificate via DNSSEC TLSA Sign your zone for DNSSEC DNSKEY, DS, RRSIG, NSEC Set up reverse DNS for a mail server\u0026rsquo;s IP PTR Hint at HTTP/3 support, alt endpoints SVCB / HTTPS Designate which servers are authoritative for your zone NS Define zone metadata (refresh timers, admin email) SOA The Pattern, If You Squint Looking at this whole list end to end, there\u0026rsquo;s a pretty obvious through-line: DNS doesn\u0026rsquo;t get redesigned, it gets extended — reactively, in response to whatever crisis the internet is having at the time. Address exhaustion → AAAA. Protocol sprawl → SRV. Phone/internet convergence → NAPTR. Cache poisoning → DNSSEC. Spam and phishing → SPF/DKIM/DMARC. CA breaches → CAA and TLSA. Slow connection setup → SVCB/HTTPS.\nThe SPF type-99 story is probably my favorite detail in all of this — a brand-new record type got formally standardized, basically nobody adopted it, and the spec was rewritten a few years later to officially say \u0026ldquo;yeah, just use TXT like everyone already was\u0026rdquo; [10]. It\u0026rsquo;s a reminder that the IANA registry isn\u0026rsquo;t a museum of successful designs — it\u0026rsquo;s a record of every idea that got tried, some of which won and some of which quietly faded out.\nThe IANA registry keeps growing [16], and there\u0026rsquo;s no reason to think it\u0026rsquo;ll stop. Whatever the next internet-wide headache turns out to be — and there\u0026rsquo;s always one brewing — there\u0026rsquo;s a decent chance the fix for it ends up as a new three-or-four-letter code in somebody\u0026rsquo;s zone file.\nSources DNS records | Cloudflare Learning Center RFC 1035 - Domain Names: Implementation and Specification (IETF Datatracker) Paul Mockapetris - Wikipedia Celebrating 30 Years of the Domain Name System (DNS) - Internet Society RFC 3596: DNS Extensions to Support IP Version 6 SRV record - Wikipedia NAPTR record - Wikipedia RFC 4034: Resource Records for the DNS Security Extensions RFC 7208 - Sender Policy Framework (SPF) for Authorizing Use of Domains in Email Discontinuing the SPF Record (Type 99) - DNSimple Blog RFC 7489 - Domain-based Message Authentication, Reporting, and Conformance (DMARC) RFC 6698: The DNS-Based Authentication of Named Entities (DANE) TLSA Protocol Certification Authority Authorization Checking: What is it, and Why Does it Matter? - DigiCert DNS Certification Authority Authorization - Wikipedia RFC 9460 - Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records) Domain Name System (DNS) Parameters - IANA DNS record types - Cloudflare Developers ","permalink":"https://cloudmato.com/posts/dns-records-explained-types-timeline/","title":"DNS Records Explained: Why So Many Types Exist (Timeline)"},{"content":"I spent a chunk of last weekend with about fifteen pricing pages open in different tabs, trying to figure out why a small side project\u0026rsquo;s API bill quietly tripled in a month. Turns out the answer wasn\u0026rsquo;t \u0026ldquo;the model got more expensive\u0026rdquo; — it was that I picked the wrong model for the job, on the wrong platform, without caching anything. So I went down the rabbit hole properly: every major LLM API, every cloud wrapper, and the budget options nobody talks about until their AWS bill shows up.\nWhy \u0026ldquo;per million tokens\u0026rdquo; doesn\u0026rsquo;t tell the whole story Every provider quotes prices per million tokens, split into input and output. That split matters more than people realize — output tokens are almost always 3 to 5 times more expensive than input tokens across every provider [1][5]. If your app generates long responses (summaries, code, reports), your bill is dominated by output pricing, not input. If you\u0026rsquo;re mostly stuffing huge documents into context and getting short answers back, input pricing is what to optimize.\nHere\u0026rsquo;s the part that\u0026rsquo;s genuinely confusing and almost nobody mentions: the same text doesn\u0026rsquo;t always cost the same number of tokens across models. Anthropic quietly noted that Claude Opus 4.7 and later use a new tokenizer that \u0026ldquo;may use up to 35% more tokens for the same fixed text\u0026rdquo; compared to older Claude models [1]. So a model that looks 20% cheaper per token on paper could end up costing more per task once you account for tokenization differences. This is exactly the kind of gotcha that makes naive price comparisons misleading.\nThe two levers that actually move the needle on your bill:\nPrompt caching – reusing a system prompt, document, or conversation history. Anthropic charges just 10% of the base input price for cache hits [1]. Google\u0026rsquo;s Gemini caching can cut input costs by up to 90% too (cached Gemini 2.5 Flash input drops to $0.03/M from $0.30/M) [3]. Batch processing – for anything that doesn\u0026rsquo;t need a response in real time (classification, bulk summarization, data labeling), nearly every provider gives you a flat 50% discount on both input and output tokens [1][2][3][5]. If you\u0026rsquo;re not using at least one of these two, you\u0026rsquo;re probably overpaying by a wide margin no matter which model you picked.\nThe big three, head to head: Claude vs GPT vs Gemini Let\u0026rsquo;s start with the frontier labs everyone defaults to. Here\u0026rsquo;s where things stood as of June 2026, per official pricing pages:\nModel Input ($/M tokens) Output ($/M tokens) Batch (in/out) Notes Claude Opus 4.8 $5.00 $25.00 $2.50 / $12.50 Cache hit $0.50/M (90% off) [1] Claude Sonnet 4.6 $3.00 $15.00 $1.50 / $7.50 1M context window included [1] Claude Haiku 4.5 $1.00 $5.00 $0.50 / $2.50 Cheapest Claude, still very capable [1] GPT-5.5 (flagship) $5.00 $30.00 $2.50 / $15.00 Cached input $0.50/M [2] GPT-5.4 $2.50 $15.00 ~50% off Cached input $0.25/M [2] GPT-5.4-mini $0.75 $4.50 ~50% off Cached input $0.075/M [2] GPT-5.4-nano $0.20 $1.25 ~50% off Cached input $0.02/M [2] GPT-4.1 nano $0.10 $0.40 — Cheapest OpenAI model overall [2][14] Gemini 3.1 Pro $2.00–$4.00 $12.00–$18.00 50% off Higher tier kicks in past 200k tokens [3] Gemini 3.5 Flash $1.50 $9.00 $0.75 / $4.50 Newest Flash, May 2026 release [3] Gemini 2.5 Flash-Lite $0.10 $0.40 — Generous free tier [3] A few things jump out here. Anthropic\u0026rsquo;s pricing is the most transparent of the three — one table, no asterisks about \u0026ldquo;short vs long context tiers.\u0026rdquo; Google\u0026rsquo;s Gemini 3.1 Pro, by contrast, doubles its price once you cross 200k tokens of context [3], which is easy to miss if you\u0026rsquo;re testing with small prompts and then ship something that processes whole PDFs.\nAlso worth knowing: Anthropic just had its biggest price cut in company history. Opus went from $15/$75 per million tokens (Opus 4.1) down to $5/$25 (from Opus 4.5 onward) — a 67% reduction [4]. That\u0026rsquo;s the kind of thing that can make a six-month-old cost analysis completely wrong, which is honestly half the reason these comparisons age so fast.\nIf you\u0026rsquo;re picking purely on output-token cost for similar \u0026ldquo;smart but not flagship\u0026rdquo; tiers, Claude Haiku 4.5 at $1/$5 and GPT-5.4-nano at $0.20/$1.25 sit near the bottom of the big-three lineup [1][2], with Gemini 2.5 Flash-Lite even cheaper at $0.10/$0.40 [3] — though Flash-Lite trades off some reasoning quality for that price.\nDoes going through AWS, Azure, GCP, or OCI actually save money? This is the question I really wanted answered, because a lot of companies route everything through their existing cloud billing for procurement reasons. Short answer: going through a cloud provider rarely makes things cheaper, and sometimes adds a real markup.\nAWS Bedrock Bedrock hosts Claude, Llama, Mistral, and Amazon\u0026rsquo;s own Nova models. The Claude pricing on Bedrock matches Anthropic\u0026rsquo;s direct pricing exactly — Opus 4.6 at $5/$25, Sonnet 4.6 at $3/$15, Haiku 4.5 at $1/$5 [5]. So no markup there\u0026hellip; unless you turn on cross-region inference for better availability, which adds a flat 10% surcharge across the board (Sonnet input goes from $3.00 to $3.30, output from $15.00 to $16.50) [5].\nWhere Bedrock gets genuinely interesting is Amazon\u0026rsquo;s own Nova models, which are dramatically cheaper than anything from Anthropic, OpenAI, or Google:\nNova Pro: $0.80 / $3.20 per million tokens Nova Lite: $0.06 / $0.24 Nova Micro: $0.035 / $0.14 [5] Nova Micro at $0.035 input is genuinely one of the cheapest \u0026ldquo;real\u0026rdquo; hosted models around — though it\u0026rsquo;s also the least capable of the bunch, so test it on your actual task before committing.\nAzure OpenAI Service Azure gives you the same models as OpenAI\u0026rsquo;s direct API, but the economics shift. The headline numbers can look cheaper — Azure\u0026rsquo;s GPT-5 listing shows $1.25/$10 versus OpenAI\u0026rsquo;s direct GPT-5.4 at $2.50/$15 [6][2] — but that\u0026rsquo;s comparing different model generations, and Azure layers on support plans ($100–$1,000+/month), networking, and infra costs that typically add 20-40% on top of listed token rates in production [6]. If you need provisioned throughput units (PTUs) for guaranteed latency, that\u0026rsquo;s a separate ~$2,448/month commitment that only pays off at serious scale [6].\nGCP Vertex AI Vertex AI is literally \u0026ldquo;Gemini, but with enterprise wrapping\u0026rdquo; — VPC Service Controls, customer-managed encryption keys, regional residency. The token pricing matches the Gemini Developer API, but if you need the Priority tier for tighter latency SLAs, expect to pay roughly 80% more than Standard tier [3]. For most projects that don\u0026rsquo;t need the compliance extras, hitting the Gemini API directly through Google AI Studio is simpler and identically priced.\nOracle Cloud Infrastructure (OCI) OCI\u0026rsquo;s Generative AI service is the odd one out — it bills per character rather than per token for most models, and hosts a narrower set: Cohere\u0026rsquo;s Command family and Meta\u0026rsquo;s Llama models [7]. The pricing range Oracle publishes runs from $0.075 per million tokens on the cheap end up to $10.68 on the premium end [7]. Honestly, I couldn\u0026rsquo;t pull exact per-model rate cards off Oracle\u0026rsquo;s pricing page (it kept blocking automated fetches), so if OCI is on your shortlist, budget time to run their cost estimator directly rather than trusting third-party summaries — the per-character billing model makes naive token-based comparisons unreliable.\nCloud platform Markup vs. direct API Best reason to use it AWS Bedrock None for Claude (0% to +10% for cross-region) Already deep in AWS billing/IAM; want Nova\u0026rsquo;s ultra-low pricing Azure OpenAI ~20-40% effective (support, infra, PTUs) Enterprise compliance, Microsoft ecosystem lock-in GCP Vertex AI 0% (Standard) / +80% (Priority) Need data residency / VPC controls with Gemini OCI Generative AI Hard to compare (per-character billing) Already on Oracle Cloud Universal Credits The pattern is consistent: cloud platforms are about compliance, procurement, and existing infrastructure — not about saving money on tokens. If your only goal is the lowest possible bill, go direct to the model provider\u0026rsquo;s API.\nThe actual cheapest options nobody talks about If you want the real budget tier, the frontier labs aren\u0026rsquo;t even in the conversation anymore. The price floor is set by open-weight models running on specialized inference providers, and one Chinese lab in particular.\nDeepSeek is the standout. DeepSeek V4 Flash costs $0.14 per million input tokens (cache miss) and $0.28 output — and if your prompt structure hits the cache, that input cost drops to $0.0028 per million tokens, a 50x reduction [8]. DeepSeek reduced cache-hit pricing to one-tenth of its launch price back in April 2026 [8], and context caching is enabled by default — if your requests share a common prefix (like a system prompt), you get the discount automatically without any extra setup. For high-volume, repetitive workloads (think: classifying thousands of similar support tickets with the same instructions), this is close to free.\nThen there\u0026rsquo;s the open-weight + specialized hardware combo:\nGroq runs Llama, Mixtral, Gemma, and DeepSeek-distilled models on custom LPU chips at 500+ tokens/second. Llama 3.1 8B Instant costs just $0.05 input / $0.08 output per million tokens [10] — and it\u0026rsquo;s fast, which matters if your app is latency-sensitive. Cerebras pushes inference even faster (1,800-2,600 tokens/sec on their wafer-scale chips), with pricing from $0.10/M for Llama 3.1 8B up to $2.30/M for GLM-4.7, plus a free tier of 1 million tokens per day [9]. Mistral\u0026rsquo;s Ministral 3B is about as cheap as it gets for a \u0026ldquo;real\u0026rdquo; hosted model: $0.04 input / $0.04 output per million tokens — effectively 8 cents round-trip [8]. Mistral Small 3 sits at $0.10/$0.30, and even their flagship Mistral Large 2 is $2/$6, undercutting both Claude Sonnet and GPT-5.4 [8]. To put the spread in perspective — and this genuinely surprised me when I plotted it out — the gap between the cheapest and most expensive \u0026ldquo;frontier-ish\u0026rdquo; models is well over 300x on output tokens alone:\nThere\u0026rsquo;s also OpenRouter, which doesn\u0026rsquo;t host models itself but acts as a single API in front of dozens of providers. Their free tier gives you 20 requests/minute and 50-1,000 requests/day across 28+ free models, including DeepSeek R1, Llama 3.3 70B, and Qwen3 Coder 480B [12]. It\u0026rsquo;s a great way to test which model fits your task before committing to a paid tier with any single provider — and the free models collection is updated as new open-weight releases land.\nWhat\u0026rsquo;s actually free (and usable)? \u0026ldquo;Free tier\u0026rdquo; claims are usually marketing fluff, but a few of these are legitimately useful for prototyping or low-volume production:\nGoogle Gemini API — the most generous of the majors. Free tier offers 1,500 requests per day on Gemini Flash models, no credit card, no expiry [13]. Both Gemini 2.5 Flash and Flash-Lite show \u0026ldquo;unlimited tokens\u0026rdquo; on the free tier (subject to rate limits) [3]. Groq — published limits of 30 requests/minute, 1,000 requests/day, and 100K tokens/day on Llama 3.3 70B [13]. Combine that with their LPU speed and it\u0026rsquo;s a solid free option for anything latency-sensitive. Cerebras — 1 million tokens per day, free, on some of the fastest inference hardware available [9]. OpenRouter — 20 RPM / up to 1,000 RPD across two dozen-plus open models, no card required [12]. OpenAI — gives new accounts about $5 in credit that expires three months after activation, but you need a credit card on file from day one [13]. Not really a \u0026ldquo;free tier\u0026rdquo; in the same sense. Anthropic — small trial credits for new accounts, plus a separate program giving qualifying open-source maintainers 6 months of Claude Max access (a $1,200 value, 10,000 spots) [13]. If you\u0026rsquo;re just prototyping, the strongest free stack right now is Gemini for general capability, Groq for speed-sensitive calls, and OpenRouter for trying out whatever new open model just dropped [13].\nSo what would I actually use? Here\u0026rsquo;s how I\u0026rsquo;d map this onto real decisions, based on everything above:\nYour situation What I\u0026rsquo;d pick Why Prototyping / hobby project Gemini free tier or OpenRouter free models Zero cost, decent quality, no card needed High-volume, repetitive tasks (classification, bulk summarization) DeepSeek V4 Flash with caching Cache hits at $0.0028/M are basically a rounding error [8] Latency-critical chat/agents Groq (Llama 3.1 8B) or Cerebras LPU/wafer-scale speed at near-zero cost [9][10] Production app needing strong reasoning, cost-conscious Claude Haiku 4.5 or GPT-5.4-mini with prompt caching Good quality-to-cost ratio, mature tooling [1][2] Best-in-class quality, cost secondary Claude Opus 4.8 or GPT-5.5 Use Batch API for non-realtime parts of the pipeline [1][2] Already deep in AWS and need Claude Bedrock, but watch the cross-region 10% surcharge Same pricing as direct, plus AWS billing/IAM integration [5] Enterprise compliance requirements Azure OpenAI or Vertex AI Standard tier Pay the markup for VPC controls, residency, support SLAs [6][3] To make this concrete: Anthropic\u0026rsquo;s own worked example shows processing 10,000 support tickets through Claude Haiku 4.5 costs about $37 at standard rates — and that drops further with caching [1]. At roughly Rs 88 to the dollar, that\u0026rsquo;s about Rs 3,250 for 10,000 conversations — genuinely hard to beat for a production support bot.\nThe honest takeaway after all this digging: DeepSeek and the open-weight providers (Groq, Cerebras, Together AI) have become the price floor that the big labs are forced to compete against [10][14]. Anthropic\u0026rsquo;s 67% Opus price cut and OpenAI\u0026rsquo;s proliferation of nano/mini variants aren\u0026rsquo;t happening in a vacuum — they\u0026rsquo;re a direct response to models that cost pennies and are \u0026ldquo;good enough\u0026rdquo; for an enormous chunk of real workloads. Whether \u0026ldquo;good enough\u0026rdquo; is actually good enough for your task is something no pricing page can tell you. You have to test it.\nSources Claude API Pricing - Anthropic Docs OpenAI API Pricing Gemini Developer API Pricing Anthropic Claude API Pricing In 2026 - CloudZero Amazon Bedrock Pricing - AWS Azure OpenAI Service - Pricing | Microsoft Azure OCI Generative AI Pricing - Oracle Models \u0026amp; Pricing - DeepSeek API Docs Cerebras Pricing Groq API Pricing - AI Pricing Guru Mistral AI Pricing Free AI Models on OpenRouter Free LLM APIs in 2026: Every Provider With Free Tier Tested - TokenMix LLM API Pricing Comparison In 2026 - CloudZero ","permalink":"https://cloudmato.com/posts/cheapest-llm-api-2026-pricing-comparison/","title":"Cheapest LLM API in 2026: Claude vs GPT vs AWS vs OCI"},{"content":"Open the Performance panel in Chrome DevTools for the first time and it looks like someone spilled a box of crayons across your screen. Dozens of colored bars, half a dozen tracks, three tabs at the bottom that all seem to show the same thing but slightly differently. No wonder most devs record one trace, get scared, and go back to console.log debugging. I did exactly that for years - until I actually sat down and learned what each piece means, and now it\u0026rsquo;s the first tool I reach for when something \u0026ldquo;feels slow.\u0026rdquo;\nWhy this panel is worth the learning curve Lighthouse gives you a score. The Performance panel gives you the why. It records a complete timeline of everything the browser does while your page runs - every JavaScript function call, every layout recalculation, every paint, every network request - and lets you scrub through it frame by frame [1].\nThink of it like the difference between a thermometer and a CT scan. A thermometer (Lighthouse, PageSpeed Insights, your Core Web Vitals dashboard) tells you something is wrong. The Performance panel tells you exactly which organ is causing the problem, and at what minute it started acting up.\nWhen you open the panel now, you\u0026rsquo;re greeted with a Live Metrics screen showing real-time Core Web Vitals - LCP, CLS, and INP - that update as you click around the page [3]. That\u0026rsquo;s new-ish, and it\u0026rsquo;s genuinely useful because you don\u0026rsquo;t even need to record a trace to get a first read on whether something\u0026rsquo;s broken.\nRecording your first trace: runtime vs. reload There are two fundamentally different things you might want to measure, and DevTools gives you a button for each:\nRecord (the circle button) - captures activity while you interact with a live page. Use this for scrolling, typing, clicking buttons, opening modals - basically anything that happens after the page has already loaded. Record and reload - captures the entire page load, from navigation to \u0026ldquo;fully loaded.\u0026rdquo; DevTools first navigates to about:blank to clear out leftover screenshots and traces, then reloads your page and automatically stops recording a couple of seconds after load finishes [3]. Before you hit record, click the gear icon to open capture settings. This is where most of the useful knobs live:\nScreenshots - captures a film strip so you can visually see what the user saw at each point in time Force garbage collection - triggers a GC run before recording starts, useful when you\u0026rsquo;re chasing memory-related jank CPU and network throttling - more on this below JavaScript samples - keeps detailed call stacks visible (this is on by default, and turning it off makes the flame chart far less useful, so don\u0026rsquo;t touch it unless you have a reason) CSS selector stats - shows which selectors are expensive during style recalculation Advanced paint instrumentation - exposes detailed paint operation data [2] I\u0026rsquo;d recommend recording a short trace first - even just 3-5 seconds of an interaction. Long recordings are harder to navigate and DevTools can get sluggish processing them. Smaller, focused recordings are easier to read and faster to analyze.\nReading the flame chart: the main thread, decoded This is the part that scares people off, so let\u0026rsquo;s slow down. The flame chart sits in the Main track and visualizes everything happening on the main thread over time [1].\nThe X-axis is time. Left to right, just like a normal timeline. The Y-axis is call stack depth. A function at the top calls a function below it, which calls another function below that. Note that this is \u0026ldquo;inverted\u0026rdquo; compared to a traditional flame graph - in DevTools, parents sit above their children, not below [6]. Each bar is a function call (or browser event). A wider bar means it took longer to execute. Colors map to categories - yellows for scripting (your JS), purples for rendering (style/layout), greens for painting, and grey for \u0026ldquo;other\u0026rdquo;/idle activity. Here\u0026rsquo;s the diagram that finally made it click for me:\nTwo terms you\u0026rsquo;ll see constantly once you start clicking on bars:\nSelf time - how long the function itself ran, excluding anything it called. Total time - self time plus everything its children did. A wide processData() bar doesn\u0026rsquo;t automatically mean processData() is the problem. It might just be a thin wrapper around JSON.parse(), which is doing all the actual work. Self time tells you who\u0026rsquo;s actually guilty; total time tells you who\u0026rsquo;s standing closest to the crime scene [6].\nCall Tree, Bottom-Up, Event Log: same data, three different lenses Once you click on a bar in the flame chart, the bottom half of the panel switches to show you details. There are three tabs down there, and honestly, figuring out when to use which one took me embarrassingly long.\nView What it shows Best for Call Tree Top-down view starting from root activities, expandable into their children Following the program\u0026rsquo;s flow - \u0026ldquo;what triggered what\u0026rdquo; Bottom-Up Inverted view, starting from the functions where time was actually spent, aggregated across the whole recording Finding your single biggest offender across the entire trace Event Log Chronological list of every event, timestamped, with filters for category and duration Finding when something happened, especially network requests and user interactions The Call Tree answers \u0026ldquo;how did we get here?\u0026rdquo; - it shows root activities and lets you drill down into which function caused the most work [1]. The Bottom-Up tab flips that around: it aggregates the Self Time of every function across all its occurrences, so the function eating the most CPU bubbles straight to the top regardless of where it was called from [2]. If you only ever learn one tab, learn this one - it\u0026rsquo;s the fastest route to \u0026ldquo;okay, this function is the problem.\u0026rdquo;\nThe Event Log is less about CPU time and more about sequence - it\u0026rsquo;s where I go when I need to know exactly when a network request fired relative to a layout shift, with filtering by Loading, Scripting, Rendering, or Painting categories [2].\nBoth Call Tree and Bottom-Up support regex search, case-sensitive matching, and \u0026ldquo;group by\u0026rdquo; options (by URL, by category, by the activity itself), which becomes essential once your trace has thousands of entries [2].\nThe 50-millisecond rule: hunting down long tasks Here\u0026rsquo;s the single most important number in this whole article: 50 milliseconds. Any uninterrupted chunk of work on the main thread that runs longer than that is officially a \u0026ldquo;long task,\u0026rdquo; and DevTools flags it with a red triangle in the corner of the bar, with the portion over 50ms shaded in red [5].\nWhy does this matter so much? Because the main thread can only do one thing at a time. While it\u0026rsquo;s busy running your 380ms onClick handler, it cannot respond to a tap, a scroll, or a key press. The user\u0026rsquo;s input just sits in a queue. This is directly why your Interaction to Next Paint (INP) score tanks - and INP is one of Google\u0026rsquo;s three Core Web Vitals.\nMy workflow for hunting these down:\nRecord the interaction that feels janky. Scan the Main track for bars with the red triangle. Click the widest one. Switch to Bottom-Up, sort by Self Time, and see which function is hogging the thread. Go fix that function. A great real-world example comes from a developer who traced a search results page and found that every API response triggered a Redux dispatch, which re-rendered the entire component tree. The long task cost for a single handler dropped from 400ms to 70ms after switching to list virtualization - and their p75 First Input Delay went from 140ms down to 25-30ms, with mobile devices seeing an 85% improvement [7]. Another case study showed a product filter going from 118ms to 22ms of blocking time, just by debouncing input and batching DOM updates [6]. These aren\u0026rsquo;t hypothetical numbers - this is what the panel is for.\nSo how do you actually fix a long task once you\u0026rsquo;ve found it? A few proven techniques:\nBreak the work into chunks using setTimeout() to push each chunk into its own task - the original, hacky-but-functional approach [5]. Use scheduler.yield() - the modern, purpose-built API. await scheduler.yield() pauses your function, lets the browser handle anything more urgent (like that input event), and then resumes exactly where it left off [5]. Fall back gracefully for browsers that don\u0026rsquo;t support it yet: function yieldToMain() { if (globalThis.scheduler?.yield) { return scheduler.yield(); } return new Promise(resolve =\u0026gt; setTimeout(resolve, 0)); } Move work off the main thread entirely with Web Workers when the work doesn\u0026rsquo;t need DOM access at all. Defer or lazy-load non-critical scripts so they don\u0026rsquo;t compete for the main thread during page load. Throttling: making your gaming laptop feel like a budget Android phone Here\u0026rsquo;s an uncomfortable truth: your dev machine is lying to you. You\u0026rsquo;re testing on a laptop with eight cores and gigabit fiber. Most of your users are on a mid-range phone with patchy 4G. The Performance panel\u0026rsquo;s throttling settings exist specifically to close that gap.\nSetting Options What it simulates CPU throttling No throttling, 4x slowdown, 6x slowdown, or a calibrated custom preset Slower processors - \u0026ldquo;4x\u0026rdquo; means everything takes 4 times longer to execute [4] Network throttling Fast 4G, Slow 4G, custom presets Real-world connection speeds and latency Calibrated CPU throttling Benchmarks your actual machine and creates a custom preset A more accurate match for low/mid-tier mobile devices [4] Worth being honest about a limitation here: DevTools can\u0026rsquo;t perfectly emulate a phone\u0026rsquo;s CPU, because mobile chips have a fundamentally different architecture (different core types, thermal throttling, etc.) than desktop CPUs [4]. The 4x/6x multiplier is a rough approximation, not a phone-in-a-box. The newer calibration feature - which benchmarks your machine and derives a more realistic preset - is a meaningful step toward closing that gap, but I\u0026rsquo;d still treat throttled results as \u0026ldquo;directionally correct\u0026rdquo; rather than \u0026ldquo;exactly what a Pixel 4a would show.\u0026rdquo;\nThat said, even rough throttling is incredibly revealing. I\u0026rsquo;ve seen interactions that felt instant on my dev machine turn into 600ms-plus freezes the moment I flipped on 6x CPU slowdown. If your team doesn\u0026rsquo;t have a \u0026ldquo;test with throttling on\u0026rdquo; habit, that\u0026rsquo;s probably the single highest-value change you can make to your testing routine today.\nLive metrics and the Insights sidebar: Core Web Vitals, built right in The Performance panel\u0026rsquo;s redesign added a Live Metrics landing page that shows LCP, CLS, and INP updating in real time as you use the page - and crucially, it shows both your local (lab) data and field data pulled from the Chrome UX Report (CrUX) side by side, so you can see how your test session compares to what real users actually experience [3] [8].\nAfter you record a trace, the Insights sidebar takes over. This is where the recent overhaul really shines - it essentially merges Lighthouse-style audits with your actual trace data [8]:\nLCP breakdown by phase - splits your Largest Contentful Paint into Time to First Byte, resource load delay, resource load time, and element render delay, so you know which phase to attack [9] Render-blocking requests - flags scripts/styles delaying first paint, often with a one-line fix suggestion like \u0026ldquo;inline this critical CSS\u0026rdquo; [9] Layout Shift clusters - groups CLS-causing shifts into \u0026ldquo;session windows\u0026rdquo; so you can see which DOM changes are responsible [9] Clickable culprits - clicking the flagged LCP element or INP target jumps you straight to that node in the Elements panel [8] For a quick \u0026ldquo;good/needs improvement/poor\u0026rdquo; gut check, the thresholds worth memorizing: LCP under 2.5 seconds is good, CLS under 0.1 is good, and INP under 200ms is good [9]. If the Insights panel flags any of these as red, that\u0026rsquo;s your starting point - don\u0026rsquo;t go hunting through the flame chart blind.\nIt\u0026rsquo;s also worth knowing about the separate Performance Monitor panel - a lightweight, always-on dashboard showing live CPU usage, JS heap size, DOM node count, and event listener count. I like opening this before I record anything, just to get a feel for whether something is leaking memory or accumulating listeners over time [15].\nCutting through the noise: ignore lists and dimming third parties A real-world trace is busy. Analytics scripts, ad tags, chat widgets, your framework\u0026rsquo;s internals - they all show up as bars competing for your attention. DevTools gives you a few ways to mute the noise:\nAdd scripts to the ignore list - right-click any entry in the flame chart and choose \u0026ldquo;Add script to ignore list.\u0026rdquo; That script\u0026rsquo;s frames collapse into a single entry, and the same ignore list applies when you\u0026rsquo;re stepping through code in the Sources panel too. The rules persist across DevTools sessions [11]. \u0026ldquo;Dim 3rd parties\u0026rdquo; checkbox - greys out third-party scripts and network requests in the trace so first-party code visually pops [10]. The Summary tab\u0026rsquo;s first/third-party table - a newer addition that breaks down time spent by first-party code, third-party code, and browser extensions, with hover-to-highlight in the trace [10]. Search with Cmd/Ctrl+F - searches activity names across the whole trace, with regex and case-sensitivity toggles [2]. There\u0026rsquo;s also a navigation change worth knowing about: DevTools now offers \u0026ldquo;Modern\u0026rdquo; vs \u0026ldquo;Classic\u0026rdquo; scrolling modes. In Classic mode, your scroll wheel zooms and Shift+scroll pans. In Modern mode, it\u0026rsquo;s the reverse - plain scrolling pans the timeline like you\u0026rsquo;d expect on any other page, and Shift+scroll zooms [10]. If scrolling in the panel has ever felt backwards to you, that setting is why.\nHonestly, this is one of those features I wish I\u0026rsquo;d known about months earlier - I spent way too long manually scrolling past webpack-bundled vendor code looking for \u0026ldquo;my\u0026rdquo; functions before I discovered the ignore list could just collapse all of it.\nAnnotating, saving, and sharing your findings Found something interesting? Don\u0026rsquo;t just screenshot it and paste it into Slack with \u0026ldquo;look at this 😬\u0026rdquo; - annotate the trace itself. Since DevTools 131, you can add annotations directly onto a recording [12]:\nLabel entries - right-click (or double-click) any bar and add a text note explaining what it is or why it\u0026rsquo;s slow Link entries - draw an arrow between two trace items to show cause-and-effect, like \u0026ldquo;this network request triggered this re-render\u0026rdquo; Time ranges - shift-click and drag to highlight a whole section, useful for marking \u0026ldquo;this is the third-party chat widget initializing\u0026rdquo; When you\u0026rsquo;re done, click the download icon in the action bar and choose Save trace. You can optionally include a copy of all script content and source maps from the page - which means whoever opens your trace later gets your actual source code and a working Sources panel, not just minified gibberish [13]. This is genuinely great for getting help: instead of describing a problem in a bug report, you attach a .json trace file and say \u0026ldquo;open this in DevTools and look at the Main track around the 2-second mark.\u0026rdquo;\nLetting Gemini read the trace with you The newest layer on top of all this is AI assistance. Chrome\u0026rsquo;s DevTools now has a \u0026ldquo;Debug with AI\u0026rdquo; option (it used to be called \u0026ldquo;Ask AI\u0026rdquo;) that appears when you right-click a trace entry, offering context-aware prompts based on whatever you clicked [14].\nWhat\u0026rsquo;s more interesting is that you\u0026rsquo;re no longer limited to asking about one isolated bar. After recording a trace, you can chat with Gemini about the entire trace - the timeline, the Insights sidebar\u0026rsquo;s findings, and even the field data - all in one conversation, before drilling into a specific event [14]. The AI can autonomously pull in relevant context (a specific long task, a render-blocking request) without you manually selecting it first.\nI\u0026rsquo;ll be honest, I was skeptical of \u0026ldquo;AI but for DevTools\u0026rdquo; - it sounded like a gimmick bolted onto an already-complex tool. But asking \u0026ldquo;why is this LCP so slow?\u0026rdquo; and getting back a plain-English explanation that points at the exact resource and phase responsible is a genuinely faster on-ramp for junior devs (or for me, on a Monday morning before coffee) than manually cross-referencing the LCP breakdown against the network waterfall.\nPutting it all together If you want one workflow to actually remember, here it is:\nOpen DevTools, go to Performance, glance at Live Metrics for an obvious red flag. Hit Record (or Record and reload) with throttling enabled to match real users. Perform the slow interaction, then stop. Check the Insights sidebar first - it might just hand you the answer. Scan the Main track for red-flagged long tasks. Click the widest offending bar, switch to Bottom-Up, sort by Self Time. Fix the actual function, re-record, and compare. Next time someone on your team says \u0026ldquo;the page just feels slow,\u0026rdquo; you don\u0026rsquo;t have to shrug and say \u0026ldquo;yeah, JS is heavy these days.\u0026rdquo; You can open a trace, point at a specific 380ms bar, and say exactly whose function that is.\nSources Performance panel: Analyze your website\u0026rsquo;s performance Performance features reference | Chrome DevTools Analyze runtime performance | Chrome DevTools Throttling | Chrome DevTools Optimize long tasks | web.dev How to Read a Flame Graph in Chrome DevTools Profiling \u0026amp; Optimizing the runtime performance with the DevTools Performance tab Brand New Performance Features in Chrome DevTools | DebugBear Performance insights: Get actionable insights on your website\u0026rsquo;s performance Improved navigation and filtering in the DevTools Performance panel Ignore List | Chrome DevTools How To Annotate A Chrome DevTools Performance Trace | DebugBear Save and share performance traces | Chrome DevTools Chat with AI assistance | Chrome DevTools Performance monitor panel | Chrome DevTools ","permalink":"https://cloudmato.com/posts/chrome-devtools-performance-panel-deep-dive/","title":"Chrome DevTools Performance Panel: Analyzing Code Execution"},{"content":"Every time someone asks me \u0026ldquo;should I use sessions or JWTs?\u0026rdquo;, I know what\u0026rsquo;s actually behind the question. They\u0026rsquo;ve read a few blog posts, seen the word \u0026ldquo;stateless\u0026rdquo; thrown around like it\u0026rsquo;s automatically better, and now they\u0026rsquo;re stuck. So let\u0026rsquo;s settle this properly - not with buzzwords, but with what\u0026rsquo;s actually happening on the wire and on your server.\nSessions: the \u0026ldquo;we keep a record at the front desk\u0026rdquo; approach Think of session-based auth like checking into a hotel. You show your ID once at the front desk, the staff verifies it, and they hand you a room key card. That card doesn\u0026rsquo;t contain your name, your passport number, or your booking details - it\u0026rsquo;s just a random number. The hotel\u0026rsquo;s computer system has all your actual information stored in their database, linked to that card number.\nThat\u0026rsquo;s exactly how session-based authentication works:\nYou log in with your username and password. The server checks your credentials, creates a session record (your user ID, roles, login time, etc.), and stores it server-side - in memory, a database, or something like Redis [2]. The server generates a random session ID and sends it back as a cookie, usually with the HttpOnly flag so JavaScript can\u0026rsquo;t touch it [3]. On every subsequent request, your browser automatically attaches that cookie. The server takes the session ID, looks it up in its session store, and goes \u0026ldquo;oh right, this is Amit, he\u0026rsquo;s logged in, here\u0026rsquo;s his data.\u0026rdquo; The important thing here: the session ID itself means nothing. It\u0026rsquo;s just a pointer. All the actual \u0026ldquo;truth\u0026rdquo; - who you are, what you can do - lives on the server [1]. If the server\u0026rsquo;s session store goes down or gets cleared, every logged-in user gets kicked out instantly, even though their cookie is technically still valid.\nThis is why session-based auth has been the default for traditional server-rendered web apps for decades. It\u0026rsquo;s simple, it\u0026rsquo;s well understood, and revoking access is trivial - just delete the row from the session table.\nJWTs: the \u0026ldquo;show your ID badge every time\u0026rdquo; approach Now imagine a different system - instead of a room key that points to a database record, you get a laminated badge. That badge has your name, your photo, your access level, and a tamper-evident hologram printed right on it. Anyone with a UV scanner can verify the hologram is genuine without calling back to the front desk.\nThat\u0026rsquo;s a JSON Web Token (JWT). A JWT is a self-contained, digitally signed string made up of three parts separated by dots: header.payload.signature [4][5].\nHeader: tells you the token type (JWT) and which algorithm was used to sign it, like HS256 or RS256 [5]. Payload: the actual claims - things like sub (user ID), role, iat (issued at), and exp (expiry time). This is just Base64Url-encoded JSON, not encrypted, so don\u0026rsquo;t put secrets in here [5][9]. Signature: the header and payload, hashed and signed with a secret key (or private key). This is what proves the token hasn\u0026rsquo;t been tampered with [4]. A decoded JWT payload might look something like this:\n{ \u0026#34;sub\u0026#34;: \u0026#34;user_8821\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Amit Kumar\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;iat\u0026#34;: 1748505600, \u0026#34;exp\u0026#34;: 1748509200 } Here\u0026rsquo;s the part that throws people off: the server doesn\u0026rsquo;t store this anywhere. When you log in, the server signs this payload and hands you the whole thing. On your next request, you send the token back (usually in an Authorization: Bearer \u0026lt;token\u0026gt; header), and the server just re-checks the signature. If the signature is valid and the token hasn\u0026rsquo;t expired, the server trusts everything in the payload - no database lookup needed [6][1].\nYou can poke around with real tokens yourself on the jwt.io debugger - paste in any JWT and it\u0026rsquo;ll decode the header and payload for you instantly [4].\nThe core difference: where does the \u0026ldquo;truth\u0026rdquo; actually live? Okay, here\u0026rsquo;s the bit that actually matters, and it\u0026rsquo;s the answer to the question in the title.\nWith sessions, the truth lives on the server. With JWTs, the truth travels with the client.\nEverything else - cookies vs headers, scalability debates, revocation headaches - is a downstream consequence of this one architectural decision. Session auth is stateful: the server has to remember something about you between requests. JWT auth is (mostly) stateless: each request carries everything the server needs to make a decision, and the server just verifies a cryptographic signature [6][1].\nThis single difference cascades into pretty much every practical trade-off you\u0026rsquo;ll hit. A request that needs a session lookup costs roughly 15ms against a Redis or database store, while verifying a JWT signature costs around 20ms but skips the storage round-trip entirely - and at scale, JWT-based systems have been benchmarked handling around 1M requests/sec across distributed servers versus roughly 500K req/sec for centralized session stores [11]. That\u0026rsquo;s not a small gap when you\u0026rsquo;re operating at scale.\nThe revocation headache nobody mentions until it\u0026rsquo;s a problem Here\u0026rsquo;s where it gets tricky, and honestly, this is the part most \u0026ldquo;JWT is better!\u0026rdquo; articles gloss over.\nWith sessions, logging a user out is instant. You delete their session record from the store, and the next request with that cookie fails immediately. Need to force-logout everyone because of a security incident? Wipe the session table. Done [2].\nWith JWTs, there\u0026rsquo;s no session record to delete - that\u0026rsquo;s the whole point of statelessness. Once a JWT is signed and handed to the client, the server has no built-in way to \u0026ldquo;take it back.\u0026rdquo; It remains valid until it expires, no matter what happens afterward [7][8]. Even if a user resets their compromised password, or an admin deactivates their account, a previously issued JWT can keep working until its exp claim says otherwise [8].\nThis isn\u0026rsquo;t a hypothetical edge case. Redis\u0026rsquo;s own engineering team wrote a piece literally titled \u0026ldquo;JSON Web Tokens (JWT) are dangerous for user sessions\u0026rdquo;, making the point that the statelessness which makes JWTs attractive is the same property that makes them risky for anything resembling a traditional login session [7].\nSo how do real systems deal with this? A few patterns have become standard:\nShort-lived access tokens (5-15 minutes) paired with longer-lived refresh tokens. If an access token leaks, the damage window is small [8]. Refresh token rotation with token families - each refresh issues a new refresh token, and if an old, already-used one shows up again, the whole family gets revoked (a strong signal of theft) [8]. Denylists - keeping a server-side list of revoked token IDs (the jti claim) in something like Redis with a TTL matching the token\u0026rsquo;s expiry [8]. Notice something? Every single one of these \u0026ldquo;fixes\u0026rdquo; reintroduces a bit of server-side state. So in practice, pure stateless JWT auth is mostly a myth once you need real-world logout, password-reset invalidation, or permission changes. What you actually get is \u0026ldquo;mostly stateless, with a small stateful safety net.\u0026rdquo; That\u0026rsquo;s not a knock on JWTs - it\u0026rsquo;s just the honest picture, and OWASP\u0026rsquo;s own guidance explicitly recommends maintaining a denylist for terminated sessions [9].\nWhere you store the token actually matters - a lot This is the other thing that trips people up: JWT vs session is a separate question from where you store the token in the browser, but the two get conflated constantly.\nA session ID is almost always stored in a cookie, typically HttpOnly and Secure, so client-side JavaScript can never read it [3]. A JWT can be stored in localStorage, sessionStorage, or a cookie - and this choice has real security consequences. If you stuff a JWT into localStorage, any successful XSS attack on your site can read it and exfiltrate it directly - there\u0026rsquo;s no browser-level protection [10]. Cookies with the HttpOnly flag are harder to steal via XSS because JavaScript simply can\u0026rsquo;t read them, though they bring their own concern: since cookies get sent automatically with every request, you need to guard against CSRF using SameSite cookie attributes or anti-CSRF tokens [10].\nThe pattern that\u0026rsquo;s gained traction recently, and one I\u0026rsquo;d genuinely recommend if you\u0026rsquo;re building something new:\nKeep the short-lived access token in memory (a JS variable or app state) - never persisted, so a page reload wipes it. Keep the refresh token in a HttpOnly, Secure, SameSite cookie - inaccessible to JS, automatically sent only to your auth endpoint. On page load, silently call your refresh endpoint to mint a new access token using that cookie [10]. It\u0026rsquo;s a bit more setup work, but it gives you the best of both - XSS can\u0026rsquo;t easily steal a long-lived credential, and CSRF protections are scoped to a single, narrow refresh endpoint.\nSessions vs JWT: the side-by-side comparison Let\u0026rsquo;s just lay it all out, because at this point you\u0026rsquo;ve got enough context to actually weigh these properly.\nAspect Session-based JWT-based Where the \u0026ldquo;truth\u0026rdquo; lives Server-side store (DB/Redis/memory) [1] Inside the signed token itself [6] State model Stateful Mostly stateless (with caveats) [1][8] Typical transport HttpOnly cookie with session ID [3] Authorization: Bearer header or cookie [4] Logout / revocation Instant - delete the session record [2] Hard - needs short expiry, denylist, or refresh rotation [7][8] Cross-domain / microservices use Needs shared session store across services [12] Any service can verify independently with the shared key/cert [6] Payload size on the wire Tiny (just an ID, ~32 bytes) Larger - full claims sent on every request [5] Scalability under load ~500K req/sec with centralized store [11] ~1M req/sec, no storage round-trip [11] Best suited for Traditional server-rendered apps, single-domain sites [13] SPAs, mobile apps, APIs, distributed/microservice systems [12][6] So which one should you actually pick? Honestly? For most \u0026ldquo;normal\u0026rdquo; web apps - a Django, Rails, or Express app rendering pages and serving a single frontend - sessions are still the simpler, more secure default. You get instant revocation, smaller attack surface, and one less thing to get wrong cryptographically [1][2].\nJWTs start to make real sense when:\nYou\u0026rsquo;ve got multiple services that need to verify identity independently without all hammering one auth database [6]. You\u0026rsquo;re building a public API consumed by third parties, mobile apps, or partners across different domains [12]. You genuinely need to scale horizontally without a shared session store becoming your bottleneck [11]. But here\u0026rsquo;s my honest take, and it might be controversial: a huge number of projects reach for JWTs because it feels \u0026ldquo;modern\u0026rdquo; or because some tutorial said so, not because they actually have the scaling or cross-service problem JWTs solve. If your app is one Node server talking to one Postgres database, a session cookie with Redis as the store will serve you just as well - probably better - with way less to worry about regarding token theft and revocation.\nA lot of production systems land on a hybrid anyway: short-lived JWT access tokens for the actual API calls (fast, statelessly verifiable), backed by a server-tracked refresh token that behaves a lot like\u0026hellip; a session [8][13]. Which, when you think about it, is kind of funny - you end up reinventing sessions, just with extra steps and a cryptographic signature on top.\nMaybe that\u0026rsquo;s the real \u0026ldquo;core difference\u0026rdquo; worth remembering: it\u0026rsquo;s less \u0026ldquo;stateful vs stateless\u0026rdquo; as an absolute, and more about where you\u0026rsquo;re willing to put your state, and how much you trust the client to carry it honestly.\nSources JWTs vs. sessions: which authentication approach is right for you? Session-Based Authentication - SuperTokens Blog Session vs Token Based Authentication: Cookies, JWT, \u0026amp; Best Practices - Authgear JWT.IO - JSON Web Tokens Introduction JSON Web Token Structure - Auth0 Docs Stateless Sessions for Stateful Minds: JWTs Explained - Auth0 Blog JSON Web Tokens (JWT) are dangerous for user sessions - Redis Blog JWT Token Lifecycle Management: Expiration, Refresh, and Revocation Strategies - skycloak JSON Web Token for Java - OWASP Cheat Sheet Series LocalStorage vs Cookies: Storing JWT Tokens Securely - Cyber Chief JWT vs Session Authentication: Real Scaling Differences - LoginRadius Session-Based Authentication vs. JSON Web Tokens (JWTs) in System Design - GeeksforGeeks JWT vs Session authentication - Logto Blog ","permalink":"https://cloudmato.com/posts/session-vs-jwt-tokens-core-difference/","title":"Session vs JWT Tokens: The Core Difference Explained"},{"content":"\u0026ldquo;Just cache it in Redis\u0026rdquo; — you\u0026rsquo;ve probably heard that in a code review, a system design interview, or a Stack Overflow comment. It\u0026rsquo;s almost a reflex at this point. But why Redis specifically? Why not a regular database with a good index, or any other in-memory store? I dug into the history, the architecture, and the current landscape of alternatives, and the story is genuinely more interesting than the meme lets on.\nWhat Redis Actually Is Let\u0026rsquo;s clear something up first. Redis is not just a cache. That\u0026rsquo;s the misconception that trips people up. Redis stands for REmote DIctionary Server [1], and it\u0026rsquo;s an in-memory data structure store — meaning it holds data in RAM rather than on disk. That one decision explains most of what makes it fast.\nBut calling it a \u0026ldquo;key-value store\u0026rdquo; undersells it. Redis natively understands a rich set of data structures [1]:\nStrings — the basic type; also used as counters and binary blobs Lists — ordered, support duplicates; perfect for queues and activity feeds Sets — unique values with O(1) membership checks Sorted Sets — unique members with a numeric score for ordering; think real-time leaderboards Hashes — a mini hashmap within a key; store objects without serializing everything Streams — append-only event logs for processing Bitmaps / HyperLogLogs — space-efficient counting and probabilistic data Geospatial indexes — find nearby locations using coordinates This is exactly why Redis dominates over Memcached for most modern use cases. Memcached is faster for raw string caching with very simple access patterns — and that\u0026rsquo;s basically the only thing it does. The moment you need a sorted leaderboard, a pub/sub channel, or a rate-limiting counter, Memcached can\u0026rsquo;t help you.\nThe Origin Story This is the part I wasn\u0026rsquo;t expecting to be genuinely good.\nSalvatore Sanfilippo — known online as antirez — is a Sicilian programmer who, in 2007, was building a real-time web analytics tool called LLOOGG [2]. The idea was clever: let website owners see their visitors\u0026rsquo; behaviour live, including which pages they visited and in what sequence. For context, Google Analytics didn\u0026rsquo;t ship real-time tracking until 2011, so antirez was legitimately ahead of the curve [3].\nThe problem? MySQL couldn\u0026rsquo;t handle it. Every page view needed to be written and queried quickly enough to display live data. Under real traffic, MySQL\u0026rsquo;s disk I/O became the bottleneck. Sanfilippo\u0026rsquo;s insight was to stop fighting disk altogether — he wrote a quick prototype in Tcl called LMDB (LLOOGG Memory Database) just to test the hypothesis [2]. It worked. So he rewrote it properly, in C.\nThat prototype became Redis. The first public release went out on April 10, 2009 [1]. His friend David Welton posted it on Hacker News. The initial response was mostly silence — except for Ezra Zygmuntowicz, a well-known Ruby on Rails developer, who immediately saw what antirez had built and helped spread the word [2].\nFrom there it moved fast. Here\u0026rsquo;s the rough timeline:\n2010 — VMware hired Sanfilippo to work on Redis full-time [1] 2012 — GitHub adopted it for activity feeds; Twitter used it for trending topics and timeline fanout [3] 2013 — Pivotal took over sponsorship when VMware restructured 2015 — Redis Labs (now Redis Inc.) became the primary commercial steward 2020 — antirez stepped back from active development And LLOOGG, the analytics tool that sparked everything? It ran on Redis until it was shut down in 2014 — handling over two billion page views, running at 350–400 commands per second, on a virtual machine that cost $150 a month [2]. A side project built to fix one developer\u0026rsquo;s database problem ended up powering one of the most widely deployed infrastructure components on the planet.\nWhy It\u0026rsquo;s So Fast Three architectural decisions make Redis what it is.\nEverything lives in RAM This is obvious once you say it out loud, but it\u0026rsquo;s worth stating clearly. Accessing RAM is roughly 100,000x faster than a spinning disk and about 10x faster than an SSD for random reads [4]. Disk-based databases manage buffer pools, page caches, and I/O queues. Redis skips all of that. You ask for a key, it\u0026rsquo;s already in memory, done.\nSingle-threaded event loop This surprises people. Doesn\u0026rsquo;t single-threaded mean slower? Not for Redis — because Redis isn\u0026rsquo;t CPU-bound, it\u0026rsquo;s I/O-bound. The single-threaded model eliminates lock contention entirely: no mutexes, no race conditions, no synchronization overhead between threads. Commands execute atomically and sequentially [4]. The event loop handles thousands of concurrent connections via non-blocking I/O (using epoll internally), so the single thread never blocks waiting for a slow client [11]. Since Redis 6.0, optional threaded I/O can offload network reads and writes to multiple threads — but the command execution itself stays single-threaded. It\u0026rsquo;s a cleaner design than it looks.\nEfficient protocol and data structures The RESP (Redis Serialization Protocol) is minimal and fast to parse. No SQL planner, no JOIN resolution, no optimizer overhead. And the data structures aren\u0026rsquo;t generic implementations — sorted sets, for example, are implemented using a skip list + hash table combination simultaneously, giving O(log N) inserts with O(1) score lookups [5].\nThe result: a standard Redis instance handles over 100,000 operations per second with median latency around 0.167 milliseconds for GET/SET operations [4]. With pipelining — batching multiple commands into one network round trip — you can push past a million ops/sec on the same hardware.\nWhat People Actually Use Redis For Caching (the obvious one) You\u0026rsquo;ve got a Postgres query that takes 180ms on every request. You cache the result in Redis with a 60-second TTL. Now 95% of reads hit RAM and return in under 1ms. This is the dominant use case — and the reason Redis is effectively default infrastructure at any company with real traffic [11].\nGitHub used this for their activity feeds early on. Instead of reconstructing a user\u0026rsquo;s timeline from database joins on every page load, they stored pre-computed timelines in Redis and updated them on write events [3].\nSession management Think about what a session holds: user ID, auth token, maybe a cart ID. It\u0026rsquo;s small, it\u0026rsquo;s read on almost every authenticated request, and it needs to be fast. Redis fits perfectly — you set a key with a TTL and it auto-expires when the session should end. No cron job to clean up stale sessions, no garbage in your main database. Instagram stored hundreds of millions of these pairs in Redis at their scale [3].\nRate limiting \u0026ldquo;How many API calls has this IP made in the last 60 seconds?\u0026rdquo; Redis answers this with a single atomic INCR + EXPIRE. No transactions, no locking, no race conditions. Twitter used this pattern to enforce API rate limits across their entire platform [3]. It\u0026rsquo;s probably the most elegant implementation of a leaky bucket pattern I\u0026rsquo;ve seen — and it\u0026rsquo;s three lines of code.\nReal-time leaderboards This is where sorted sets earn their keep. ZADD leaderboard 9500 \u0026quot;player:88\u0026quot; adds a score. ZRANGE leaderboard 0 9 WITHSCORES REV returns the top 10 instantly, no re-sorting needed, O(log N) inserts [5]. This is how mobile games handle millions of concurrent score updates without choking a relational database. It just works.\nPub/Sub messaging Redis has a built-in publish/subscribe messaging system — publishers send to named channels, subscribers receive in real time [9]. It\u0026rsquo;s not meant to replace Kafka for durable, replayed event streams (Redis Pub/Sub doesn\u0026rsquo;t persist messages), but for lightweight real-time notifications — \u0026ldquo;new chat message\u0026rdquo;, \u0026ldquo;price update\u0026rdquo;, \u0026ldquo;follow event\u0026rdquo; — it works beautifully and adds zero infrastructure overhead.\nVector search and AI workloads More recently, Redis has leaned hard into AI use cases. Redis can store vector embeddings alongside regular data, making it useful for semantic caching of LLM responses and retrieval-augmented generation (RAG) pipelines [4]. If you\u0026rsquo;ve been building anything with LangChain, LangGraph, or mem0, you\u0026rsquo;ve likely already seen Redis pop up as a memory backend. The new Vector Sets type in Redis 8.0 was written by antirez himself specifically for this use case [13].\nThe License Drama (It Actually Matters) This part is relevant if you\u0026rsquo;re making an architecture decision today. Honestly, this got messy.\nIn March 2024, Redis Inc. changed the project\u0026rsquo;s license from the permissive BSD 3-Clause to a dual RSALv2 / SSPLv1 model [6]. Neither is OSI-approved open source. The SSPLv1 was originally created by MongoDB to target cloud providers — it essentially says: if you offer this as a managed service, you must also open-source your entire platform.\nThe target was obvious. AWS ElastiCache was generating hundreds of millions in revenue from Redis while Redis Inc. struggled with a ~1% conversion rate from free to paid users [6]. The frustration is completely understandable. The execution was a disaster.\nThe community backlash was immediate. Linux distributions (openSUSE, Fedora) began dropping Redis from their repos. Within weeks, former Redis maintainers forked the last BSD-licensed version and launched Valkey under the Linux Foundation in April 2024, backed by AWS, Google Cloud, Oracle, Alibaba, and Ericsson [7].\nThen antirez himself — who had stepped back from the project in 2020 — rejoined Redis Inc. in November 2024. He pushed hard for a course correction. On May 1, 2025, Redis 8.0 launched under a tri-license: RSALv2, SSPLv1, or AGPLv3 [13]. The AGPLv3 is OSI-approved open source — a genuine step back toward the community. Antirez wrote about it openly: \u0026ldquo;I\u0026rsquo;m happy that Redis is open source software again.\u0026rdquo;\nDid the reversal fix the damage? Not really. Valkey had already achieved critical mass by then [14].\nThe Alternatives Valkey The most significant fork in recent open-source history. Valkey is Redis 7.2.4 under the Linux Foundation — fully protocol-compatible, drop-in replacement for almost every use case [7]. AWS migrated ElastiCache to it. Google Cloud Memorystore supports it. Aiven migrated 15,000 servers between May and August 2024.\nValkey 8.1 reportedly runs ~8% more ops/sec than Redis OSS, cuts P99 latency by 22%, and uses 20% less memory [7]. For most teams, this is the safest migration path — change the package name, swap the image, done.\nDragonfly A ground-up rewrite in C++, not a Redis fork. Dragonfly uses modern concurrency primitives — fibers, SIMD, shared-nothing architecture — to utilise all CPU cores efficiently [8]. The claimed throughput is 25x better than Redis on the same hardware, with meaningfully lower memory footprint per dataset.\nThe caveat: it\u0026rsquo;s ~95-98% Redis-compatible. There are edge-case gaps in administrative commands [8]. For new projects with extreme throughput requirements, it\u0026rsquo;s worth a benchmark run. For migrating existing setups, test your specific command usage before committing.\nKeyDB Multi-threaded Redis. Same protocol, same commands, but it runs on all your cores. Acquired by Snap, which brought proper engineering resources to it [8]. The FLASH feature (tiered storage to SSD) is genuinely interesting for datasets that don\u0026rsquo;t fully fit in RAM. If multi-threading on familiar Redis semantics is what you want, KeyDB is a proven option.\nMemcached The elder statesman. Memcached predates Redis and is still genuinely fast for pure string caching with minimal operational overhead [10]. No persistence, no sorted sets, no Lua scripting, no pub/sub — just fast key-value storage. If all you need is \u0026ldquo;cache this database result for 60 seconds\u0026rdquo; and nothing more, Memcached\u0026rsquo;s simplicity is a feature. But for any use case beyond basic string caching, Redis (or Valkey) gives you substantially more at similar latency.\nRedis 8.0 Valkey 8.1 Dragonfly KeyDB Memcached License RSALv2/SSPL/AGPL BSD 3-Clause BSL 1.1 BSD 3-Clause BSD Persistence ✓ ✓ ✓ ✓ ✗ Sorted Sets ✓ ✓ ✓ ✓ ✗ Pub/Sub ✓ ✓ ✓ ✓ ✗ Multi-threaded cmd exec ✗ ✗ ✓ ✓ ✓ Redis protocol compat Native Native ~95–98% Native ✗ Cloud managed Redis Cloud AWS / GCP / Aiven Self-hosted mostly Self-hosted AWS / GCP Which One Should You Pick? Not a single answer, but some honest shortcuts:\nAlready on Redis, it\u0026rsquo;s working — upgrading to Redis 8.0 under AGPLv3 is fine. The AGPLv3 only affects you if you\u0026rsquo;re wrapping Redis in a managed service and selling it. Starting fresh, AWS or GCP — use Valkey via ElastiCache or Memorystore. Same protocol, cleaner licensing, 20% cheaper on AWS [7]. Extreme throughput requirements and new project — benchmark Dragonfly seriously. Simple caching only — Memcached is not a wrong answer if you truly don\u0026rsquo;t need the extras. Need multi-threading on Redis protocol — KeyDB. The weird footnote to all of this: LLOOGG — the analytics tool that started everything — was shut down over a decade ago. The side project antirez built to fix one developer\u0026rsquo;s MySQL performance problem now runs inside nearly every serious tech stack on the planet. He stepped away in 2020, came back in 2024, watched a fork become the default on major cloud providers inside twelve months, and then helped push Redis back toward open source.\nYou genuinely can\u0026rsquo;t plan for that arc.\nEnd\nSources Redis - Wikipedia Story: Redis and its creator antirez — Brachiosoft Blog History of Redis: From Side Project to Industry Standard — OneUptime Complete Guide to Redis in 2026 — DragonflyDB Redis sorted sets | Docs — redis.io Redis tightens its license terms, pleasing no one — The Register Valkey Turns One: How the Community Fork Left Redis in the Dust — Momento 8 Best Redis Alternatives — DragonflyDB Complete Guide to Redis Publish Subscribe — GeeksforGeeks The Good and the Bad of Redis In-Memory Database — AltexSoft What is Redis Explained? — IBM Redis Switches to SSPLv1: Restrictive License Sparks Fork — InfoQ Redis is open source again — antirez.com Redis Returns to Open Source under AGPL License: Is It Too Late? — InfoQ Redis is now available under the AGPLv3 open source license — redis.io Understanding Redis Threading — DEV Community ","permalink":"https://cloudmato.com/posts/why-use-redis-history-alternatives/","title":"Why Redis? History, Use Cases \u0026 Best Alternatives"},{"content":"Your app is getting slower over time. Scroll position jumps. Tabs are consuming 800 MB of RAM. You open Task Manager and watch Chrome eat memory like it\u0026rsquo;s buffet day. Something is leaking — but where? The Chrome DevTools Memory tab is sitting right there, and most developers either ignore it or open it once, get confused by \u0026ldquo;Shallow Size\u0026rdquo; and \u0026ldquo;Retainers,\u0026rdquo; and quietly close it. This guide is for people who want to actually use it.\nWhy Memory Leaks Are Sneaky in JavaScript JavaScript is garbage collected. V8 — Chrome\u0026rsquo;s JavaScript engine — automatically frees memory once it decides an object is no longer reachable [1]. The algorithm is simple in principle: if nothing holds a reference to an object, it can be collected.\nThe problem? JavaScript makes it very easy to accidentally keep references alive. An event listener. A closure that captured a variable. A detached DOM node still pointed to by a global array. The garbage collector can\u0026rsquo;t help you here — the objects are reachable, just not intentionally. Memory management in JavaScript is technically \u0026ldquo;automatic,\u0026rdquo; but that doesn\u0026rsquo;t mean leak-free [1].\nThree patterns account for the vast majority of JavaScript memory leaks [2]:\nForgotten event listeners — you remove an element from the DOM but never called removeEventListener. The listener (and everything in its closure) stays alive. Closures holding big objects — a function that closed over a large variable, kept alive because an event or timer still references that function. Detached DOM nodes — elements removed from the page but still referenced by a JavaScript object somewhere. The Memory tab has four distinct tools. Each one is optimized for finding a different kind of problem. Opening the wrong one wastes your time.\nThe Memory Tab: Four Tools, Not One Open DevTools → Memory. You\u0026rsquo;ll see four radio buttons [3]:\nHeap snapshot — a point-in-time photo of everything on the heap Allocation instrumentation on timeline — records allocations as they happen, over time Allocation sampling — a lightweight, statistical version of the above Detached elements — lists DOM nodes that are orphaned but still referenced Each one answers a different question. Here\u0026rsquo;s the quick decision matrix before we go deep:\nTool Best for Overhead Duration Heap Snapshot \u0026ldquo;What is alive right now?\u0026rdquo; High (pauses GC) Instant Allocations on Timeline \u0026ldquo;What got allocated and never freed?\u0026rdquo; Medium Short session Allocation Sampling \u0026ldquo;Which functions allocate the most?\u0026rdquo; Low Long sessions Detached Elements \u0026ldquo;Which removed DOM nodes are still held?\u0026rdquo; Low Instant Heap Snapshot This is the one most people try first. It captures a complete snapshot of the JavaScript heap at a single point in time — every object, every DOM node, every string, every reference [3].\nYou take one, look at it, and if you\u0026rsquo;ve never done this before, you immediately feel overwhelmed by thousands of constructors and numbers. That\u0026rsquo;s normal. Here\u0026rsquo;s what to actually look at.\nThe Views Switch the dropdown at the top-left of the snapshot result. There are three views [4]:\nSummary — default. Lists every constructor function that created objects currently alive on the heap. The columns that matter:\nShallow size — memory occupied by the object itself, ignoring references Retained size — memory that would be freed if this object (and everything it alone keeps alive) was garbage collected [5] Comparison — take a second snapshot after performing some action, then compare it against the first. Shows # New, # Deleted, # Delta. This is where leaks become obvious. Comparing heap snapshots is the most reliable way to confirm a leak is real [6].\nContainment — a bird\u0026rsquo;s-eye view of the object graph starting from roots like window and closures. Good for when you already suspect a specific object and want to trace exactly what\u0026rsquo;s keeping it alive [4].\nShallow vs Retained Size — This Part Actually Matters Honestly, this is the most confusing concept in this whole panel, but once it clicks it\u0026rsquo;s simple.\nShallow size is just the object itself. A plain JS object {} with a few properties might be 64 bytes shallow. Retained size is everything that would be freed if this object disappeared [5]. If that plain object holds a reference to an Array of 10,000 items, its retained size is 64 bytes + all that array memory.\nWhen hunting leaks, sort by Retained size, not Shallow. An object with 64 bytes shallow but 50 MB retained is exactly what you\u0026rsquo;re looking for.\nThe Classic Leak-Hunting Workflow with Heap Snapshots Open the Memory tab, select Heap snapshot Click Take snapshot — this is your baseline Perform the action you suspect leaks (open a modal, navigate a route, click a button repeatedly) Do the reverse (close the modal, navigate back) Take a second snapshot In snapshot 2, change the view to Comparison Sort by # Delta descending If objects that should have been collected (the modal\u0026rsquo;s internal components, the route\u0026rsquo;s views) still show a positive delta after you reversed the action — you\u0026rsquo;ve found your leak. The Retainers section at the bottom tells you what\u0026rsquo;s holding them.\nWhen to use Heap Snapshot: You already know something is leaking and you want to identify exactly what it is. Also good for before/after analysis — e.g., \u0026ldquo;does this refactor actually reduce memory usage?\u0026rdquo;\nAllocations on Timeline Heap snapshots show you the state of memory. The Allocation Timeline shows you the story — what got allocated, when, and whether it survived [7].\nWhen you click Record, DevTools periodically takes micro-snapshots (roughly every 50ms) throughout your session. Each allocation appears as a vertical bar [7]:\nBlue bar — objects allocated here are still alive when you stopped recording Gray bar — objects allocated here were subsequently garbage collected ✓ The ones you care about are the blue ones that shouldn\u0026rsquo;t be blue. If you click a button, perform an action, and see a cluster of blue bars that never turns gray no matter how long you wait — those objects are not being freed.\nHow to Use It Select Allocation instrumentation on timeline Hit Start Perform the suspect action (scroll, click, navigate) Stop recording Look at the timeline for persistent blue bars Click on any bar to filter the Constructor list below it — shows only objects allocated during that window that are still live The difference from Heap Snapshot is significant. The Timeline tells you when allocations happened, which makes it much easier to connect an allocation to a specific user interaction. You might see a clear spike every time a certain button is clicked — that\u0026rsquo;s your culprit\u0026rsquo;s timestamp [8].\nWhen to use Allocations on Timeline: You\u0026rsquo;re trying to isolate which user interaction is causing growth. You have a general sense that something leaks but you don\u0026rsquo;t know what action triggers it.\nOne Gotcha The overhead here is real. DevTools is effectively snapshotting the heap every 50ms [7]. Don\u0026rsquo;t record a 10-minute session with this tool — you\u0026rsquo;ll get a massive profile that\u0026rsquo;s painful to analyze, and your app will be noticeably slower during recording. Keep recordings short and focused on the specific interaction you\u0026rsquo;re investigating.\nAllocation Sampling Here\u0026rsquo;s the one most developers skip, which is a mistake. Allocation Sampling is the lightweight version of the Timeline — it uses statistical sampling instead of recording every allocation [3].\nThe trade-off: less precise, but almost zero overhead. You can record for minutes or hours without significantly impacting your app\u0026rsquo;s performance. This matters because some memory growth is gradual — it takes 20 minutes of use before it becomes visible. The Timeline would be unusable for that. Allocation Sampling is designed for it.\nWhat It Shows The results look like a flame chart / call tree. You get a breakdown of heap memory allocated by each function over the duration of the profile, including allocations that were later freed [3]. The default view is \u0026ldquo;Heavy (Bottom Up)\u0026rdquo; — the functions that allocated the most memory are listed at the top.\nThis answers a different question than the other tools: not \u0026ldquo;what is leaked\u0026rdquo; but \u0026ldquo;which code is allocation-heavy.\u0026rdquo; A function that allocates 200 MB even if most of it gets collected is still something worth looking at from a performance standpoint.\nWhen to use Allocation Sampling: Long-running profiling sessions where you can\u0026rsquo;t afford the overhead of the Timeline. When you want to understand which functions are the biggest allocators (not necessarily leaking, just expensive). When you\u0026rsquo;re doing memory optimization rather than leak hunting.\nDetached Elements This one is more targeted than the others. Rather than showing you the whole heap, it specifically finds DOM nodes that have been removed from the page\u0026rsquo;s DOM tree but are still referenced by JavaScript [9].\nWhy does this matter? When you call element.remove() or clear innerHTML, you expect those nodes to be garbage collected. But if any JS variable, array, closure, or event handler still holds a reference to that element, V8 cannot collect it [2]. The node is \u0026ldquo;detached\u0026rdquo; — no longer visible in the page, but very much alive in memory.\nThis is extremely common in single-page applications. Think about a component framework that keeps a cache of previously rendered elements, or a list that stores references to rows you\u0026rsquo;ve already removed from the viewport.\nCommon Code That Creates Detached Elements // Classic detached node scenario let detachedList = []; function addAndRemove() { const el = document.createElement(\u0026#39;div\u0026#39;); document.body.appendChild(el); detachedList.push(el); // saved the reference document.body.removeChild(el); // removed from DOM // el is now detached — detachedList holds it alive } The detachedList array keeps every div alive even though they\u0026rsquo;re all gone from the page [2].\nUsing the Detached Elements Profiler Select Detached elements Click Get detached elements (or Take snapshot — the label varies by Chrome version) The resulting list shows every orphaned DOM node still referenced by your JavaScript [9] Each entry is expandable — you can see parent/child nodes that are also being retained Click the \u0026ldquo;Analyze\u0026rdquo; button to see which JS objects hold references to them The \u0026ldquo;Retainers\u0026rdquo; panel at the bottom is key. It shows you the exact variable or closure that\u0026rsquo;s keeping the node alive. That\u0026rsquo;s where you go to write the fix [10].\nWhen to use Detached Elements: Any time you suspect DOM nodes aren\u0026rsquo;t being cleaned up. Especially useful in SPAs with dynamic rendering, virtual lists, or modals that are shown/hidden rather than created/destroyed. Also run it after a major refactor to confirm cleanup logic is working [9].\nNote — detached elements aren\u0026rsquo;t always a leak. A framework might legitimately cache a few nodes for performance reasons. Context matters. But if you have hundreds of detached elements growing over time, that\u0026rsquo;s a problem [9].\nPutting It Together: A Real Debugging Session You notice your SPA\u0026rsquo;s memory climbs from 50 MB to 400 MB after a few minutes of use. Here\u0026rsquo;s how I\u0026rsquo;d approach it:\nStart with Heap Snapshot comparison. Take a baseline snapshot, use the app for 2 minutes, take another. Switch to Comparison view, sort by delta. If you see a clear class or constructor type growing — you know what to investigate.\nIf the snapshot shows DOM nodes growing, switch to Detached Elements and snapshot. This confirms whether removed elements are piling up.\nIf you need to identify the trigger, use Allocations on Timeline for a focused 30-second recording. Perform one specific interaction and watch for blue bars that don\u0026rsquo;t turn gray.\nIf you need to profile a longer session (say, a background polling loop running for several minutes), use Allocation Sampling. Let it run, stop it, and see which functions are the biggest allocators.\nDon\u0026rsquo;t try to use all four simultaneously. Pick the one that answers your immediate question, act on what you find, then re-profile.\nA Few Things Worth Knowing Force garbage collection first. Before taking a snapshot for comparison, click the trash-can icon (\u0026ldquo;Collect garbage\u0026rdquo;) in the Memory tab. This runs GC manually so you\u0026rsquo;re not comparing snapshots that differ only because GC hasn\u0026rsquo;t run yet [3].\nThe Distance column in Heap Snapshot shows the number of hops from the GC root to the object. Objects with a very small Distance (like 2 or 3) are directly reachable from global scope — often signs of globals you forgot to clean up.\n(string) and (array) constructors often dominate Summary view. This is usually normal. Don\u0026rsquo;t panic about them unless their retained size is growing between snapshots.\nRetainer chains can be long. Sometimes you\u0026rsquo;ll chase a chain of 6–7 objects before reaching the actual variable that\u0026rsquo;s keeping something alive. That\u0026rsquo;s just how reference graphs work. Follow it.\nEnd\nSources Memory management — MDN Web Docs Causes of Memory Leaks in JavaScript and How to Avoid Them Memory panel overview — Chrome DevTools Record heap snapshots — Chrome DevTools The difference between Shallow Size and Retained Size Find memory leaks by comparing heap snapshots — DevTools Tips How to Use the Allocation Timeline Tool — Chrome DevTools Isolating memory leaks with Chrome\u0026rsquo;s Allocation Timeline — LogRocket Get detached DOM elements to investigate memory leaks — DevTools Tips Debug DOM memory leaks — Microsoft Edge DevTools Fix memory problems — Chrome DevTools Memory terminology — Chrome DevTools ","permalink":"https://cloudmato.com/posts/chrome-devtools-memory-tab-guide/","title":"Chrome DevTools Memory Tab: A Practical Guide"},{"content":"Your web app is probably doing things you\u0026rsquo;ve never actually debugged. Pages being prerendered before the user clicks anything, form submissions silently queued while offline, push notifications arriving at a sleeping service worker, sessions cryptographically bound to hardware keys — all of this happens through Chrome\u0026rsquo;s background service APIs, running completely invisibly. The Background Services panel in Chrome DevTools is where you finally get to watch all of it.\nFinding the Panel Open DevTools (F12 or right-click \u0026gt; Inspect), go to the Application tab, and scroll the left sidebar until you hit the \u0026ldquo;Background Services\u0026rdquo; section. You\u0026rsquo;ll find a list covering essentially every \u0026ldquo;invisible\u0026rdquo; API Chrome supports:\nBack/forward cache Background fetch Background sync Bounce tracking mitigations Notifications Payment handler Periodic background sync Push messaging Reporting API Device bound sessions What makes this panel genuinely useful is that several tabs can record events for up to three days, even when DevTools is not open [1]. Set a recording, walk away, come back the next morning — the events are there. That\u0026rsquo;s unusual for browser tooling, and it changes how you debug async behavior that\u0026rsquo;s hard to reproduce on demand.\nBack/Forward Cache The back/forward cache (bfcache) is one of those browser features that\u0026rsquo;s been around since 2020 but still surprises developers — usually when they realise their page isn\u0026rsquo;t using it. The idea: when a user navigates away from your page, Chrome keeps the full page — JavaScript heap, DOM, scroll position, everything — frozen in memory. If they hit the back button, Chrome thaws and restores it instantly, with no network round trips, no re-rendering, no JS re-execution [2].\nThe measured performance gains are significant. Bfcache restores can happen in under 100ms — faster than anything achievable through conventional load optimisation [2]. It essentially makes back/forward navigation free.\nThe frustrating part used to be not knowing whether your page was actually eligible. That improved substantially in Chrome 123 with two things:\nThe Test back/forward cache button in DevTools (Application \u0026gt; Back/forward cache \u0026gt; Run Test). Chrome navigates you away and back, then tells you explicitly whether bfcache worked — and if it didn\u0026rsquo;t, exactly why. The notRestoredReasons API, which lets you query programmatically what blocked bfcache on any given navigation [3]. The panel categorises blockers as \u0026ldquo;Actionable\u0026rdquo; (things you can fix) or \u0026ldquo;Not actionable\u0026rdquo; (browser-level decisions). Common actionable blockers include:\nunload event listeners attached to the page — replace them with pagehide handlers that check event.persisted Open IndexedDB transactions that haven\u0026rsquo;t closed before navigation Cache-Control: no-store headers — historically a hard block, but Chrome began allowing bfcache for these pages under limited conditions in early 2025, completing its full rollout to 100% of users in April 2025 [4] If the test flags your page as ineligible, read the reasons carefully. The panel is specific enough to be actionable — it tells you which frame, which script, which reason. Not just \u0026ldquo;blocked.\u0026rdquo;\nSpeculative Loads If bfcache optimises the back button, Speculative Loads targets the forward one. The Speculation Rules API lets you instruct Chrome to prefetch or prerender pages you think the user is likely to navigate to next [5].\nThe difference between the two modes matters:\nMode What Chrome does Resource cost Prefetch Downloads HTML + some subresources Low — no JS execution Prerender Fully renders the page in a hidden background process Higher — full page lifecycle Prerender is the impressive one. When it works and the user clicks, the navigation is instant — the page is already fully rendered, the Largest Contentful Paint has already happened, JavaScript has already run.\nReal-world results back this up. Monrif, an Italian media group, applied prerendering to their highest-traffic article URLs in early 2025 and saw up to a 17.9% improvement in LCP and a peak 8.9% increase in user engagement in some audience segments [6]. Those aren\u0026rsquo;t typical performance win numbers. That\u0026rsquo;s the kind of uplift that moves product dashboards.\nTo debug in DevTools: Application \u0026gt; Background Services \u0026gt; Speculative loads. The section has three views [7]:\nSpeculative loads — current speculative status for the page, plus which URLs it\u0026rsquo;s attempting to preload Rules — the JSON speculation rule sets currently active on the page Speculations — a table of every attempt with target URL, action (prefetch or prerender), which rule triggered it, and current status If a speculation fails, clicking the row in the Speculations table shows the full failure reason. Useful for tracking down URL mismatches, opted-out origins, or prerender quota limits. When a prerender is actively running, DevTools also lets you switch its context to inspect the hidden background render directly — running Network, Console, or Performance inside a page the user hasn\u0026rsquo;t navigated to yet.\nWorth flagging: WordPress 6.8 (released March 2025) baked the Speculation Rules API into WordPress core. If you run a WordPress site and you haven\u0026rsquo;t looked at this panel recently, there\u0026rsquo;s a real chance speculations are already running [7].\nBackground Fetch Standard fetch() requests die the moment the tab closes. That\u0026rsquo;s by design — it would be alarming if arbitrary web pages could run network requests in the background indefinitely. Background Fetch is the explicitly permitted version of that — a download that survives tab closure, with user-visible progress and native cancellation controls.\nThe use cases that motivated the API were large media files: a podcast app needing a 300MB episode, a streaming service caching offline content, a game pre-loading assets. But there\u0026rsquo;s a newer angle that\u0026rsquo;s increasingly relevant. Web.dev\u0026rsquo;s guidance on using Background Fetch for AI model downloads covers the case where model weights can be several gigabytes — completely impractical to download inside a page lifecycle, but manageable as a background fetch [9].\nThe flow works through the service worker:\nCall registration.backgroundFetch.fetch() with an ID and the list of resources The browser handles the download, showing native progress UI (users can pause or cancel) When complete, the browser wakes your service worker to process the downloaded response To debug: Application \u0026gt; Background Services \u0026gt; Background Fetch \u0026gt; click Record. Trigger a fetch from your page, watch events log to the table, click any event to see full detail. The three-day recording window means you can verify a background download that completes well after you\u0026rsquo;ve closed DevTools [1].\nOne caveat: Background Fetch is a Chromium-specific feature [10]. It doesn\u0026rsquo;t exist in Firefox or Safari. If you\u0026rsquo;re building cross-browser, plan a fallback path.\nBackground Sync Background Sync is the counterpart — it\u0026rsquo;s about sending data reliably, not receiving it. The scenario: a user submits a form, queues a message, or fires an analytics event while on a flaky connection. Standard fetch fails silently. Background Sync queues the request and delivers it the next time the device has a reliable connection, no manual retry required [8].\nThe Workbox Background Sync module makes this practical. Its BackgroundSyncPlugin hooks into fetch failure events — specifically, when a network error throws an exception, not when you get a 4xx or 5xx response — and queues failed requests to IndexedDB for later retry with exponential backoff.\nA testing mistake that trips people up: the \u0026ldquo;Offline\u0026rdquo; checkbox in the Service Workers section of the Application panel does not block service worker network requests. It only affects page-level fetch calls. If you\u0026rsquo;re testing background sync behaviour, you need to actually disable your network adapter or use the Network panel\u0026rsquo;s throttling presets. Testing with the service worker offline checkbox will make sync appear to work when it shouldn\u0026rsquo;t.\nTo debug: Application \u0026gt; Background Services \u0026gt; Background Sync \u0026gt; click Record. Trigger background sync activity, watch events populate the table with tag name, origin, and timestamp.\nPush Messaging and Notifications These two work together, so it makes sense to look at them side by side. Push Messaging is how your server delivers a message to a user\u0026rsquo;s browser even when they\u0026rsquo;re not on your site. Notifications is how that message gets shown to the user.\nThe debugging problem with push is timing. You need a server to send it, delivery is asynchronous, and the service worker processes it in the background. The Push Messaging tab records these events for three days — so you can set a recording before triggering a push from your server and inspect exactly what arrived, in what order, with what payload [1].\nFor Notifications: Application \u0026gt; Background Services \u0026gt; Notifications \u0026gt; click Record. When your service worker calls self.registration.showNotification(), the event logs with full detail — title, body, icon, badge, actions, every option you passed in.\nThere\u0026rsquo;s a shortcut that doesn\u0026rsquo;t require your server at all: the Service Workers section (just above Background Services in the sidebar) has a Push input field and button. Type a payload, click Push, and DevTools fires a synthetic push event at your registered service worker. Ideal for verifying your push handler logic without standing up server infrastructure.\nPayment Handler The Payment Handler API lets a web app act as a payment method inside the standard Web Payments flow [14]. Rather than redirecting users out to a third-party payment processor, your service worker intercepts the payment request and handles the transaction in-context — cleaner UX for wallets, buy-now-pay-later services, and web-based payment apps.\nTwo service worker events drive the whole thing:\ncanmakepayment — fires when a merchant calls new PaymentRequest(). Your service worker responds with whether it can handle this particular payment. paymentrequest — fires when the user selects your payment app from the browser payment sheet and taps confirm. This is where you do the actual transaction. To debug: Application \u0026gt; Background Services \u0026gt; Payment Handler \u0026gt; click Record. Both events log to the table with full detail on the payment method data and merchant info.\nPractical local development note: the Payment Request API requires HTTPS, but Chrome exempts localhost by default. You can test locally without setting up a self-signed certificate.\nBounce Tracking Mitigations Honestly, this is the most niche tab in the panel. But if your work touches analytics, ad tech, SSO flows, or anything involving cross-site redirects, you need to be aware of it — because it can silently delete state you depend on.\nBounce tracking is a privacy evasion technique. A user clicks a link, gets transparently bounced through a tracker domain in under a second, and that tracker gets to set or read cookies in a first-party context — even with third-party cookies fully blocked. It\u0026rsquo;s why blocking third-party cookies alone doesn\u0026rsquo;t solve cross-site tracking.\nChrome\u0026rsquo;s mitigation identifies sites that exhibit these patterns and deletes their stored state entirely — cookies, localStorage, cache storage, all of it [11]. The DevTools tab at Application \u0026gt; Background Services \u0026gt; Bounce Tracking Mitigations lets you force-run the deletion check manually and see exactly which site origins had their state removed.\nTo test it:\nIn chrome://flags, set \u0026ldquo;Bounce Tracking Mitigations\u0026rdquo; to Enabled With Deletion Block third-party cookies in Chrome Settings \u0026gt; Privacy and security Application \u0026gt; Background Services \u0026gt; Bounce Tracking Mitigations \u0026gt; click Force Run The Issues tab will also surface a warning for suspicious redirect chains: \u0026ldquo;Chrome may soon delete state for intermediate websites in a recent navigation chain.\u0026rdquo; If your OAuth or SSO implementation triggers that warning, you have an investigation to run. Legitimate auth redirects can look like bounce tracking if the hop timing is similar.\nReporting API The Reporting API sounds boring until you realise it\u0026rsquo;s giving you visibility into silent production failures you have no other mechanism to observe [12]. The API collects CSP violations, deprecated API calls, network errors, Permission Policy violations, and crash reports, then sends them to an endpoint you configure.\nConfiguration is a single response header:\nReporting-Endpoints: default=\u0026#34;https://yoursite.com/csp-reports\u0026#34; Chrome batches reports and delivers them to that URL. The DevTools panel (Application \u0026gt; Background Services \u0026gt; Reporting API) breaks this down into three sections:\nReports table — every pending and sent report, with type, URL, timestamp, and delivery status Report body — click any report row to see the full JSON payload Endpoints — a summary of configured endpoints and whether Chrome successfully reached them The status column is the most useful part in practice. Reports flow through states: Queued → Pending → Success (or MarkedForRemoval if they fail). If reports sit in \u0026ldquo;Pending\u0026rdquo; indefinitely, something is wrong with your endpoint — commonly a CORS misconfiguration, a 4xx response, or a typo in the header value.\nCSP violations are the most common use case and probably the best argument for wiring up the Reporting API even if you\u0026rsquo;re not doing anything else with it. Without it, you deploy a content security policy and get zero signal about what\u0026rsquo;s being blocked in production. With it, you get a structured log of every violation across every real user session.\nDevice Bound Sessions This is the newest entry in the panel and arguably the most security-significant feature Chrome has shipped in years. Device Bound Session Credentials (DBSC) addresses a specific, well-understood attack: session cookie theft.\nThe attack works like this. Malware on a user\u0026rsquo;s machine, a compromised browser extension, or a local privilege escalation lets an attacker export session cookies. Those cookies, replayed from the attacker\u0026rsquo;s machine, authenticate as the victim — MFA or not, because the MFA was already satisfied and the cookie is the credential.\nDBSC breaks this by cryptographically binding the session to the specific physical device [13]. At login, Chrome generates a key pair and stores the private key in the device\u0026rsquo;s secure hardware — a Trusted Platform Module (TPM) when one is available. Sessions use short-lived cookies. When a cookie expires, Chrome must prove possession of the private key before the server issues a fresh one. An attacker who steals the cookie cannot prove possession of a key that never left the device.\nFor developers implementing DBSC, the server-side changes are [14]:\nInclude a Secure-Session-Registration header in your login response, specifying the refresh endpoint URL and session configuration Expose a refresh endpoint that validates the cryptographic key possession proof before issuing new cookies DBSC became generally available for Windows users on Chrome 146 in early 2026, with macOS expansion announced shortly after [14]. A second origin trial opened in October 2025 to collect real-world implementation feedback.\nIn DevTools, the Application panel\u0026rsquo;s Device Bound Sessions section lets you view active DBSC sessions, inspect their bound domains, and delete them — useful for verifying your logout flow terminates sessions correctly, and for confirming that session binding is registered for the expected origin. Full debugging tooling is still being built out; for now, Chrome histograms and network response headers fill the gaps the panel doesn\u0026rsquo;t yet cover.\nThe underlying spec is being developed through the W3C Web Application Security working group, so DBSC is being designed for eventual cross-browser adoption — not a permanent Chrome-only feature.\nBetween bfcache giving you near-instant back navigation, speculative loads prerendering pages before users click, background sync ensuring no user action gets silently dropped on a flaky connection, and DBSC preventing session hijacking at the hardware level — the Background Services panel is quietly one of the most important and least-explored sections in all of Chrome DevTools.\nEnd\nSources Debug background services | Chrome DevTools Back/forward cache | web.dev Back/forward cache notRestoredReasons API | Chrome for Developers Enabling bfcache for Cache-Control: no-store | Chrome for Developers Prerender pages in Chrome for instant page navigations | Chrome for Developers How Monrif improved engagement by 8.9% and reduced LCP by 17.9% with Speculation Rules prerender | web.dev Debug speculation rules with Chrome DevTools | Chrome for Developers workbox-background-sync | Chrome for Developers Download AI models with the Background Fetch API | web.dev Introducing Background Fetch | Chrome for Developers Help test bounce tracking mitigations | Chrome for Developers Monitor your web application with the Reporting API | Chrome for Developers Device Bound Session Credentials (DBSC) | Chrome for Developers Device Bound Session Credentials now available on Windows | Chrome for Developers Web-based Payment Handler API | MDN Web Docs ","permalink":"https://cloudmato.com/posts/chrome-devtools-background-services/","title":"Chrome DevTools Background Services: Complete Guide"},{"content":"Open DevTools, click on Application, and look at the left sidebar. Cookies, Local Storage, Session Storage, IndexedDB, Cache Storage, Shared Storage, Background Services\u0026hellip; it\u0026rsquo;s a lot. I\u0026rsquo;ve seen senior developers reach for localStorage for everything — auth tokens, shopping carts, even megabytes of API responses — simply because it\u0026rsquo;s the one they know. That\u0026rsquo;s not always wrong, but it\u0026rsquo;s rarely the best choice. Let\u0026rsquo;s actually go through what each of these mechanisms does, where you\u0026rsquo;d find it in DevTools, and — more importantly — when you should reach for it instead of the other six options sitting right next to it.\nWhy the Application panel even matters Before browsers had a unified storage inspector, debugging \u0026ldquo;why isn\u0026rsquo;t my data persisting\u0026rdquo; usually meant console.log-ing your way through guesswork. The Application panel in Chrome DevTools changed that — it gives you a single place to view, edit, and manually delete cookies, local storage, session storage, IndexedDB databases, cache storage entries, service workers, and even newer Privacy Sandbox features like Shared Storage and Interest Groups [1].\nThat matters because every one of these storage types behaves differently — different size limits, different lifetimes, different sync/async behaviour, different visibility to the server. Picking the wrong one isn\u0026rsquo;t just a style choice; it can blow up your app\u0026rsquo;s performance, leak data across tabs, or quietly get wiped by the browser without you ever knowing why. Let\u0026rsquo;s get into it.\nThe lay of the land — quick comparison Before going deep into each one, here\u0026rsquo;s the cheat sheet I wish someone had handed me years ago:\nMechanism Capacity (rough) Survives tab close? Sent to server? Sync or async? Best for Cookies ~4KB per cookie Depends on expires Yes, automatically Sync (document.cookie) Auth/session tokens, server-read flags sessionStorage ~5MB No (per tab) No Sync Wizard/form state, one-off page data localStorage ~5–10MB Yes No Sync (blocking) User prefs, small caches, feature flags IndexedDB 100s of MB–GBs Yes No Async Offline data, large structured records Cache Storage (Service Worker) Shares origin quota Yes No Async Cached network requests, offline assets OPFS (Origin Private File System) Shares origin quota Yes No Async/sync (in workers) In-browser databases, file-heavy apps Shared Storage Small, write-only reads Yes No (cross-site, privacy-preserving) Async Privacy-safe cross-site measurement Capacities and behaviours come straight from the comparison data Chrome and MDN publish, and they roughly match what I see poking around in DevTools myself — Session/Local Storage hover around 5MB per origin, cookies are capped near 4KB, and IndexedDB can balloon into hundreds of megabytes or more depending on disk space [2].\nNow let\u0026rsquo;s actually walk through each one — what it\u0026rsquo;s for, and a real scenario where I\u0026rsquo;d pick it.\nCookies — the only one your server can actually read Here\u0026rsquo;s the thing almost everyone already knows but somehow still gets wrong: cookies are the only client storage mechanism that gets sent to the server automatically with every matching HTTP request [2]. That\u0026rsquo;s a feature when you need server-side session validation, and a performance tax when you\u0026rsquo;re stuffing megabytes of junk into them.\nIn DevTools, you\u0026rsquo;ll find them under Application → Storage → Cookies → (pick an origin). You can edit values inline, add new ones, filter by name, and check flags like HttpOnly, Secure, and SameSite right there in the table [1].\nReal-world use case: authentication sessions. When a user logs in, the server sets an HttpOnly cookie containing a session ID. Because it\u0026rsquo;s HttpOnly, JavaScript can\u0026rsquo;t read it (so an XSS bug can\u0026rsquo;t steal it directly), and because it\u0026rsquo;s a cookie, it rides along on every request so the server can validate the session without you writing a single line of client-side code to \u0026ldquo;remember\u0026rdquo; the user.\nA second, very current use case: consent and personalization flags that the server needs at render time — like which A/B test bucket a user is in, or whether they\u0026rsquo;ve accepted a cookie banner — because the server needs to know before it builds the HTML.\nIt\u0026rsquo;s also worth knowing that the third-party cookie story has been a rollercoaster. Google originally announced it would kill third-party cookies entirely, then walked that back in mid-2024 — Chrome now shows users a \u0026ldquo;choice\u0026rdquo; prompt instead of blocking them outright [3][4]. Safari and Firefox already block third-party cookies by default, so if your app depends on cross-site cookies for tracking or embeds, you\u0026rsquo;re already living in a partially-cookieless world whether Chrome forces it or not [4]. And regardless of first- or third-party, cross-site cookies must carry SameSite=None; Secure — that\u0026rsquo;s been a hard requirement since Chrome 80 [4].\nlocalStorage — the easy drawer everyone over-stuffs localStorage is a synchronous key-value store, scoped to the origin, that survives browser restarts. You\u0026rsquo;ll find it under Application → Storage → Local Storage → (origin), where DevTools lets you add, edit, and delete entries directly in a table [1].\nWhat it\u0026rsquo;s genuinely good for:\nUser preferences — theme (dark/light), language, layout density Small feature flags or \u0026ldquo;don\u0026rsquo;t show this tooltip again\u0026rdquo; markers Lightweight caching of small, infrequently-changing API responses (think: a list of country codes, not a user\u0026rsquo;s entire order history) Where I\u0026rsquo;ve seen it go wrong: people store JWTs in localStorage for \u0026ldquo;convenience,\u0026rdquo; not realizing that any script running on the page — including a third-party script pulled in via a compromised npm package or an XSS hole — can read it. Cookies with HttpOnly at least put a wall between your token and arbitrary JavaScript. If you must store a token client-side and can\u0026rsquo;t use cookies, at least understand you\u0026rsquo;re trading XSS-resistance for convenience.\nThe other gotcha: localStorage is synchronous and blocks the main thread. Write a few hundred KB of JSON into it on every keystroke of a text editor, and you\u0026rsquo;ll feel your UI stutter. I\u0026rsquo;ve actually watched a \u0026ldquo;simple\u0026rdquo; autosave feature tank input responsiveness because someone was doing localStorage.setItem(JSON.stringify(hugeObject)) on every oninput event. That\u0026rsquo;s exactly the kind of \u0026ldquo;it works on my machine\u0026rdquo; bug the Application panel helps you catch — open the Storage tab, watch the size grow in real time, and you\u0026rsquo;ll spot the problem before your users do.\nsessionStorage — the one tab won\u0026rsquo;t share with its neighbor sessionStorage looks identical to localStorage in DevTools (same table, same edit/delete UI, just a different sidebar entry [1]), but it has one crucial difference: it\u0026rsquo;s scoped to a single tab, and it disappears when that tab closes. Even two tabs pointing at the exact same origin won\u0026rsquo;t share sessionStorage data — that surprises a lot of people the first time they hit it.\nReal-world use cases:\nMulti-step forms / checkout wizards — store \u0026ldquo;step 2 of 4\u0026rdquo; data so a refresh doesn\u0026rsquo;t nuke the user\u0026rsquo;s progress, but you don\u0026rsquo;t want it lingering forever Preventing duplicate form submissions — set a flag when a form is submitted, check it before allowing a resubmit One-time onboarding flows that should reset if the user opens the app fresh in a new tab Honestly, this is the most under-used of the bunch. People default to localStorage out of habit even when the data genuinely has no business surviving past the current tab session — which then means you have to remember to clean it up yourself later. Let the browser do that for you.\nIndexedDB — when you actually need a database in the browser This is where things get more interesting — and, honestly, this is where it gets trickier too. IndexedDB is a full transactional, asynchronous, NoSQL-ish database built into the browser, capable of storing structured data, blobs, and files, with support for indexes and range queries [2][5]. In DevTools it lives under Application → Storage → IndexedDB, where you can drill into individual databases, object stores, and even individual records [1].\nReal-world use cases I\u0026rsquo;d actually reach for it:\nOffline-first apps — note-taking apps, to-do apps, email clients that need to work on a flaky connection Caching large API response sets — think a project management tool caching thousands of tasks so the UI feels instant Storing files/blobs client-side — image editors, PDF viewers, anything dealing with binary data that\u0026rsquo;s too big for localStorage\u0026rsquo;s string-only, ~5MB world The trade-off is that IndexedDB\u0026rsquo;s API is famously clunky — raw IndexedDB code is full of nested callbacks and onsuccess/onerror handlers that read like something from 2010 (because, well, it kind of is). Most teams wrap it with a library like Dexie.js or idb to make it bearable. If you\u0026rsquo;re debugging it, the Application panel\u0026rsquo;s IndexedDB viewer is genuinely one of the more pleasant parts of DevTools — you can expand object stores, inspect records, and even delete a database entirely without writing a line of code.\nOne bit of history that explains why IndexedDB won: Chrome used to also support Web SQL Database, a SQLite-backed relational API. It was deprecated and fully removed from Chromium 119 onward because it was never standardized — only Chromium ever fully implemented its spec [6]. The official guidance now is to use the Web Storage APIs or IndexedDB for most cases, and for apps with serious relational/performance needs, to use SQLite compiled to WebAssembly running on top of the Origin Private File System [6] — which conveniently brings us to our next section.\nCache Storage and Service Workers — building things that work offline This is the pairing that powers Progressive Web Apps. A service worker is a script that sits between your page and the network, intercepting requests, and the Cache API / Cache Storage is where it stashes the actual response objects [7][8]. In DevTools:\nApplication → Application → Service Workers shows registration status, scope, and lets you trigger updates, force activation, or simulate going offline [9] Application → Storage → Cache Storage is a read-only browsable list of everything cached via the Cache API — you can inspect, filter by URL, and delete individual entries [1][7] Real-world use case: a news site that wants articles to be readable on the subway with no signal. The service worker intercepts navigation requests, checks Cache Storage first, and falls back to the network only when needed. Or think of Twitter/X\u0026rsquo;s \u0026ldquo;you\u0026rsquo;re offline\u0026rdquo; screen that still shows your last-loaded timeline — that\u0026rsquo;s Cache Storage doing its job.\nThe key conceptual thing to understand — and I genuinely didn\u0026rsquo;t get this clearly until I\u0026rsquo;d debugged it the hard way — is that the Cache API is a completely separate mechanism from the regular HTTP cache. The HTTP cache is governed by response headers and browser heuristics; the Cache Storage API is entirely code-driven — you decide what gets cached, when, and for how long [8][7]. That\u0026rsquo;s powerful, but it also means you are responsible for invalidating stale entries; the browser won\u0026rsquo;t do it for you based on Cache-Control headers the way it does for the HTTP cache.\nA debugging tip that saved me a frustrating afternoon: unregistering a service worker does not clear its caches. They\u0026rsquo;re independent. If your \u0026ldquo;fix\u0026rdquo; isn\u0026rsquo;t showing up after you killed the service worker, check Cache Storage separately — or just hit the big red Clear storage button under Application → Storage, which unregisters service workers and wipes all associated storage in one click [9][10]. That single button has saved me more \u0026ldquo;why is my old code still running\u0026rdquo; headaches than I\u0026rsquo;d like to admit.\nThe newer (and weirder) ones: OPFS, Shared Storage, and Storage Buckets The Application panel keeps growing because the web platform keeps growing. A few newer entries worth knowing about:\nOrigin Private File System (OPFS) — a sandboxed, origin-scoped filesystem with byte-level, high-performance file access, including synchronous reads/writes from Web Workers [11]. It\u0026rsquo;s become the home of choice for in-browser SQLite (via WASM) and is used by local-first projects like RxDB and ElectricSQL [11]. The catch? DevTools doesn\u0026rsquo;t have native OPFS support yet — you need a third-party extension like OPFS Explorer to browse its file tree from inside DevTools [12]. Shared Storage — part of the Privacy Sandbox initiative, this lets sites store cross-site data without exposing it directly to JavaScript — you can only extract aggregate insights through privacy-preserving \u0026ldquo;worklets.\u0026rdquo; Real use cases include measuring unique ad campaign reach or rotating creatives without relying on third-party cookies [13]. You can inspect the raw key-value pairs under Application → Storage → Shared Storage [1][13]. Storage Buckets — an emerging API that lets you partition an origin\u0026rsquo;s storage into separate \u0026ldquo;buckets\u0026rdquo; with their own quota and persistence behavior, so you can, say, mark a bucket of critical offline data as persistent while letting a bucket of disposable cache data get evicted first under pressure [14]. You probably won\u0026rsquo;t touch most of these day-to-day unless you\u0026rsquo;re building ad-tech or heavy offline-first apps — but it\u0026rsquo;s worth knowing they exist next time you see an unfamiliar entry in that sidebar and wonder what it\u0026rsquo;s doing there.\nExtension Storage — debugging chrome.storage from DevTools If you build or debug Chrome extensions, this one\u0026rsquo;s a hidden gem. Under Application → Storage → Extension Storage, DevTools lists every installed extension alongside its chrome.storage.local and chrome.storage.sync data as inspectable, editable key-value pairs [17].\nThe two storage areas behave very differently. chrome.storage.local stores data on the current machine — fast, no size limit beyond the overall extension quota. chrome.storage.sync writes data that Chrome silently syncs across every device where the user is signed in [18]. From a user\u0026rsquo;s perspective that sounds magical. From a debugging perspective it means a value you cleared on your dev machine might reappear seconds later because Chrome synced it back from another device — a genuinely confusing situation if you don\u0026rsquo;t know to look here.\nWhat makes the DevTools panel actually useful: you can click into any key, edit the value live, and immediately test how your extension responds to different stored states — without needing to write a content script or open the extension\u0026rsquo;s background page. It also auto-refreshes, so you can watch chrome.storage.onChanged events land in real time as your extension writes new values.\nStorage Buckets — telling the browser what to throw away first The Storage Buckets API is Chromium\u0026rsquo;s answer to a very real production problem: storage pressure eviction is all-or-nothing by origin. If the browser decides it needs space, it clears your entire origin\u0026rsquo;s best-effort storage, regardless of whether it\u0026rsquo;s throwing away a 200 KB user preference file or a 50 MB document the user spent an hour editing [14].\nStorage Buckets fix this by letting you partition storage into named buckets with independent quota limits, eviction priority, and persistence flags [14]. The mental model:\nconst criticalBucket = await navigator.storageBuckets.open(\u0026#34;user-docs\u0026#34;, { durability: \u0026#34;strict\u0026#34;, persisted: true, }); const cacheBucket = await navigator.storageBuckets.open(\u0026#34;temp-cache\u0026#34;, { durability: \u0026#34;relaxed\u0026#34;, persisted: false, }); The user-docs bucket is marked strict + persisted: true — the browser won\u0026rsquo;t touch it under pressure. temp-cache is relaxed + persisted: false — fair game for eviction. Each bucket gets its own IndexedDB, Cache Storage, and file handles, completely isolated from the default bucket [14].\nIn Application → Storage → Storage Buckets you can inspect each named bucket, see what\u0026rsquo;s inside it (which IndexedDB databases, which cache entries live in it), and verify that your persistence settings are what you think they are before you assume critical data is safe.\nPrivate State Tokens — anti-fraud without the surveillance Private State Tokens (previously called Trust Tokens) are a Privacy Sandbox API designed to let an issuer — say, a site that already knows you\u0026rsquo;re a real human — vouch for you on a different site, without linking your identities or enabling cross-site tracking [19].\nThe basic flow: a trusted issuer (perhaps your bank, a CAPTCHA provider, or Google itself) issues you cryptographically signed tokens stored by the browser. When you visit a third-party site that partners with that issuer, the site can redeem one token as a signal that the browser trusts you\u0026rsquo;re a real person — without learning who you are or where you came from [19]. The token proves authenticity, not identity.\nIn Application → Storage → Private State Tokens, DevTools shows you which issuers have stored tokens in the current browser profile and how many tokens remain for each issuer. Tokens are issued in batches and are single-use on redemption, so a depleted count doesn\u0026rsquo;t necessarily mean something went wrong — it means the site successfully used them. The Network panel also logs individual issuance and redemption requests so you can trace the full flow [19].\nAs a developer you\u0026rsquo;re likely to encounter these if you\u0026rsquo;re integrating an anti-fraud or bot-detection service that has moved off third-party cookies, or if you\u0026rsquo;re building a Chrome extension that works with issuers like Google\u0026rsquo;s reCAPTCHA v4.\nInterest Groups — how the browser runs its own ad auction This one is the deepest Privacy Sandbox concept in the Application panel, and it only makes sense if you understand what problem it\u0026rsquo;s solving. Currently, when you visit a product page, the retailer drops a third-party cookie so that ad networks can follow you around the web and retarget you. That model is being killed by third-party cookie deprecation.\nInterest Groups (part of the Protected Audience API, formerly FLEDGE) replace it: instead of the ad network tracking you externally, the browser itself joins you to interest groups locally and runs the bidding auction on-device [20]. Your browsing history never leaves your machine. The advertiser knows the ad was served to \u0026ldquo;someone who visited running shoes pages\u0026rdquo; — but they don\u0026rsquo;t know who or when, and they can\u0026rsquo;t link it to you across other sites.\nIn Application → Storage → Interest Groups, DevTools shows you every interest group the current page has asked your browser to join, including the owner, the bidding logic URL, and the list of ads associated with that group [20]. The event timeline at the top logs joined, bid, win, and leave events, which is indispensable when you\u0026rsquo;re debugging why a Protected Audience auction isn\u0026rsquo;t serving the expected creative.\nA practical note: if you open the Application panel after loading a page, you won\u0026rsquo;t see the join events because they already fired. Refresh the page with DevTools already open to capture the full sequence [20].\nQuotas, eviction, and why your data sometimes just\u0026hellip; vanishes Ever had a user report \u0026ldquo;my data disappeared\u0026rdquo; and had no idea why? This is usually the answer: storage isn\u0026rsquo;t infinite, and the browser will evict data it decides it doesn\u0026rsquo;t need.\nChromium-based browsers generally cap an origin\u0026rsquo;s storage at a percentage of total disk space — and the specific ceiling depends on whether the origin has been granted \u0026ldquo;persistent\u0026rdquo; storage or is operating in \u0026ldquo;best-effort\u0026rdquo; mode [15]. In best-effort mode, when the browser comes under storage pressure, it starts evicting the least recently used origins first — and it skips any origin that\u0026rsquo;s called navigator.storage.persist() and been granted persistence [15].\nYou can check exactly where your app stands using the navigator.storage.estimate() API, which returns both your current usage and your quota — both of which fluctuate based on how much free disk space the device actually has [16]. And in DevTools, the Application → Storage overview literally draws you a pie chart of how your origin\u0026rsquo;s quota is divided across cookies, IndexedDB, cache storage, and so on [1] — plus a slider to simulate a smaller quota, which is brilliant for testing how your app behaves when storage gets tight before a real user ever hits that wall.\nPractical debugging checklist I run through whenever storage behaves weirdly:\nOpen Application → Storage and look at the usage pie chart — is one storage type unexpectedly huge? Check whether the origin has been granted persistence (navigator.storage.persisted()) — if not, eviction under pressure is fair game Use the quota override slider to simulate low-storage conditions and watch what breaks Hit Clear storage to get a clean slate, then reproduce the issue from scratch For service-worker issues specifically, check Cache Storage and Service Workers separately — clearing one doesn\u0026rsquo;t clear the other [9] So which one do I actually pick? Here\u0026rsquo;s the decision framework I actually use when starting a new feature that needs to remember something on the client:\nDoes the server need to read this on every request? → Cookie. Nothing else gets sent automatically [2]. Is it small (a few KB), simple key-value, and should outlive the tab? → localStorage. Is it small, simple, and should die when the tab closes? → sessionStorage. Is it structured, large, binary, or does it need querying/indexing? → IndexedDB (probably wrapped in Dexie or idb). Are you caching network responses for offline use? → Cache Storage, driven by a service worker. Do you need a real file system or an in-browser SQL database? → OPFS, likely paired with SQLite-WASM [6][11]. Are you trying to do cross-site measurement without violating user privacy? → Shared Storage [13]. Do you have critical data that must survive storage eviction? → Storage Buckets with persisted: true [14]. Building or debugging a Chrome extension? → Extension Storage (chrome.storage.local / chrome.storage.sync) [17][18]. Doing ad-tech work with the Privacy Sandbox? → Private State Tokens (anti-fraud signals) and Interest Groups (on-device ad auctions) [19][20]. Scenario Pick this Why \u0026ldquo;Remember me\u0026rdquo; login session Cookie (HttpOnly, Secure, SameSite) Server needs it, and JS shouldn\u0026rsquo;t be able to read it Dark mode toggle localStorage Small, simple, should persist across visits Multi-step signup form sessionStorage Should vanish if abandoned in this tab Offline-capable to-do app IndexedDB + Cache Storage Structured data + cached app shell News site readable on the subway Service Worker + Cache Storage Pre-cache articles, serve from cache when offline In-browser spreadsheet/SQL tool OPFS + SQLite-WASM Needs real file I/O and relational queries Privacy-safe ad reach measurement Shared Storage Cross-site insight without exposing raw identifiers Critical data that can\u0026rsquo;t be evicted Storage Buckets (persisted: true) Survives storage pressure; disposable data evicted first Extension prefs synced across devices chrome.storage.sync Chrome syncs it automatically; inspect in Extension Storage Bot/fraud detection without cookies Private State Tokens Browser-issued anti-fraud signals, privacy-preserving Interest-based ads without tracking Interest Groups (Protected Audience) On-device auction; browsing history never leaves the browser None of these are mutually exclusive, by the way — most serious web apps use several of these together. A PWA might use cookies for auth, localStorage for theme prefs, IndexedDB for offline content, and Cache Storage for the app shell, all at once. The Application panel is exactly the tool that lets you see all of that working (or not working) side by side, in one place, without writing a single debug console.log.\nNext time something \u0026ldquo;just isn\u0026rsquo;t saving,\u0026rdquo; skip the guesswork — open DevTools, click Application, and actually look. Nine times out of ten, the answer is sitting right there in plain sight.\nSources Application panel overview | Chrome DevTools | Chrome for Developers Browser Storage Explained: LocalStorage vs SessionStorage vs IndexedDB vs Cookies - DEV Community Third-Party Cookie Deprecation Testing and Debugging - Chromium Third-Party Cookie Deprecation: The 2026 Guide | Ethyca 9 differences between IndexedDB and LocalStorage - DEV Community Deprecating and removing Web SQL | Blog | Chrome for Developers CacheStorage - Web APIs | MDN Service workers and the Cache Storage API | Articles | web.dev Debug Progressive Web Apps | Chrome DevTools | Chrome for Developers Tips and Tricks for Debugging Service Workers The origin private file system | Articles | web.dev OPFS Explorer - Chrome DevTools extension - GitHub Shared Storage overview | Privacy Sandbox | Chrome for Developers Not all storage is created equal: introducing Storage Buckets | Blog | Chrome for Developers Storage quotas and eviction criteria - Web APIs | MDN Estimating Available Storage Space | Blog | Chrome for Developers View and edit extension storage | Chrome DevTools | Chrome for Developers chrome.storage API Reference | Chrome for Developers Private State Tokens | Privacy Sandbox | Chrome for Developers Protected Audience API overview | Privacy Sandbox | Chrome for Developers ","permalink":"https://cloudmato.com/posts/chrome-devtools-storage-mechanisms-explained/","title":"Chrome DevTools Storage: Every Mechanism Explained (With Use Cases)"},{"content":"I get why this question keeps coming up. A WebSocket stays open, remembers who you are, and lets the server push data to you without you asking for it again and again. So why are we still firing off a hundred separate HTTP requests for a single page load when we could just open one persistent pipe and be done with it? Honestly, the question sounds smarter than most people give it credit for — and the answer is not \u0026ldquo;because HTTP is better.\u0026rdquo; It\u0026rsquo;s a lot more nuanced than that.\nWait, isn\u0026rsquo;t a stateful connection obviously more efficient? On paper, yes. Once a WebSocket connection is established through that initial HTTP \u0026ldquo;upgrade\u0026rdquo; handshake, both the client and server can send data to each other at any time, with very little framing overhead per message [1]. No repeating headers, no cookies riding along on every request, no re-establishing who-you-are on every single exchange. After the handshake, WebSocket data frames carry minimal protocol overhead compared to HTTP, where every request and response drags along headers like cookies, user-agent strings, and cache-control directives [2].\nSo the instinct makes sense: if the connection already knows who I am and stays open, why re-introduce myself a hundred times per page?\nHere\u0026rsquo;s the catch though — the thing that makes WebSockets good at real-time communication (statefulness) is the exact same thing that makes them expensive to run at scale. It\u0026rsquo;s not a free lunch. It\u0026rsquo;s a trade you\u0026rsquo;re making, and most of the time, for a regular web page, that trade doesn\u0026rsquo;t pay off.\nThe hidden cost of \u0026ldquo;the server remembers you\u0026rdquo; When you make a regular HTTP request, REST APIs are stateless — the server doesn\u0026rsquo;t track client details between requests, which makes it trivial to add more servers and spread the load around [3]. Any server in your fleet can answer any request because nobody is \u0026ldquo;remembering\u0026rdquo; anything about you between calls.\nA WebSocket flips that completely. The server has to keep track of every single connection — who\u0026rsquo;s connected, what state they\u0026rsquo;re in, what they\u0026rsquo;ve subscribed to — for as long as that socket lives. That\u0026rsquo;s not free:\nEach open WebSocket connection eats up roughly 2–10 KB of memory just sitting there idle, and that adds up fast once you\u0026rsquo;re talking about hundreds of thousands of users [4]. Every connection also claims a file descriptor on the server, and operating systems have hard limits on how many of those you can have open at once [4]. The server burns CPU just keeping connections alive — sending heartbeats, handling reconnects, processing whatever trickles through [5]. Now multiply that by \u0026ldquo;one socket per page load.\u0026rdquo; If your site gets a few thousand concurrent visitors, you\u0026rsquo;ve suddenly got a few thousand long-lived, memory-hungry, stateful connections that your servers have to babysit — for pages that, realistically, just needed to fetch some JSON and render it once.\nThe load balancer headache nobody mentions Here\u0026rsquo;s where it gets genuinely annoying for backend teams. Because a WebSocket is stateful, the server holding that connection is the only server that knows what\u0026rsquo;s going on with that client. That means WebSocket connections require session affinity (aka \u0026ldquo;sticky sessions\u0026rdquo;) in load-balanced environments [4]. Once a client connects to server B, every future interaction for that client has to keep going to server B — your load balancer can\u0026rsquo;t just shrug and route it wherever\u0026rsquo;s free.\nThat sounds manageable until you actually run it in production:\nWhen server B crashes, every single client pinned to it loses its session state at once [6]. Rebalancing load across your fleet becomes much harder because connections can\u0026rsquo;t just be freely shuffled around [6]. Rolling deployments turn into a disruptive event, since draining a server for an update means forcibly disconnecting every sticky client attached to it [6]. The \u0026ldquo;fix\u0026rdquo; for this is to externalize session state into something like Redis so any server can pick up any client [6] — which is absolutely doable, but notice what just happened: you went from \u0026ldquo;no shared state needed\u0026rdquo; (HTTP) to \u0026ldquo;now I need a distributed state store to make my stateful protocol behave statelessly.\u0026rdquo; That\u0026rsquo;s a lot of extra moving parts to build a page that, with HTTP, would\u0026rsquo;ve just worked out of the box.\n\u0026ldquo;But HTTP wastes so many connections!\u0026rdquo; — not really, anymore This is the part of the argument that I think trips people up the most, because it\u0026rsquo;s stuck in a 2009 understanding of HTTP. Yes, browsers historically capped you at 6 concurrent connections per domain under HTTP/1.1 [9], and yes, that did mean a chatty page could feel like it was queuing requests behind each other.\nBut two things changed that picture a lot:\nHTTP/1.1 keep-alive. The \u0026ldquo;Connection: keep-alive\u0026rdquo; mechanism lets the browser reuse the same TCP connection for multiple requests instead of opening a fresh one each time [8], which already cuts out a huge chunk of the \u0026ldquo;overhead\u0026rdquo; people imagine HTTP has. HTTP/2 multiplexing. With HTTP/2, the browser opens a single TCP connection per domain and runs multiple request/response \u0026ldquo;streams\u0026rdquo; over it concurrently [9]. That essentially erases the old per-domain bottleneck — you get most of the efficiency people love about WebSockets (one connection, many exchanges) without giving up statelessness. Here\u0026rsquo;s a side-by-side that makes the real trade-offs a lot clearer:\nAspect HTTP/1.1 (keep-alive) HTTP/2 WebSocket Connections per page Up to 6 per domain [9] 1 per domain (multiplexed) [9] 1 (persistent) Server has to \u0026ldquo;remember\u0026rdquo; you No No Yes — for the connection\u0026rsquo;s lifetime [3] Can be cached by CDNs/proxies Yes [12] Yes [12] No — there\u0026rsquo;s nothing to cache, it\u0026rsquo;s a live stream Server can push without being asked No (request must come first) No Yes, at any time [2] Works behind strict corporate firewalls Yes (port 443 is always open) Yes Often blocked or needs fallback [15] Scaling model Stateless — any server, anywhere Stateless — any server, anywhere Needs sticky sessions or shared state [4][6] Looking at that table, the question kind of answers itself: HTTP/2 already solved the \u0026ldquo;too many connections\u0026rdquo; problem without asking you to give up statelessness. You get to keep caching, easy horizontal scaling, and firewall-friendliness — and you only reach for a WebSocket when you need its one genuinely unique superpower: the server pushing data to you when it decides something happened, not when you ask.\nYou\u0026rsquo;d lose caching — and that\u0026rsquo;s a much bigger deal than it sounds This is the one I think gets underrated the most. Using WebSockets for everything means you can\u0026rsquo;t cache anything, and that quietly drives your server costs way up [3]. Think about what a normal page load actually involves — your CSS, your images, your API responses for things that rarely change, your user avatar, your product listings. A huge chunk of that is identical for thousands of users and barely changes minute to minute.\nWith plain HTTP, CDNs and reverse proxies sit in front of your servers and cache all of that, serving repeat requests straight from the edge without your origin server breaking a sweat [12]. That\u0026rsquo;s a structural advantage baked into the stateless request/response model — because nobody\u0026rsquo;s remembering anything, any cache, anywhere, can serve the answer.\nA WebSocket is a live, two-way stream. There\u0026rsquo;s no \u0026ldquo;response\u0026rdquo; to cache — there\u0026rsquo;s just an ongoing conversation that\u0026rsquo;s unique to that one connection. The moment you push everything through sockets, you\u0026rsquo;ve thrown away one of the cheapest, most battle-tested performance tools the web has: the humble HTTP cache.\nThen there\u0026rsquo;s the random corporate firewall that just says no Ever built something that worked perfectly on your home Wi-Fi and then completely fell apart the moment someone tried it from their office network? WebSockets run into this constantly. Most web proxies and restrictive corporate firewalls will straight-up block WebSocket connections, often because they\u0026rsquo;re configured to only allow plain HTTP traffic on ports 80 and 443 through a transparent proxy [15][16].\nEven when the port is right, the single most common cause of WebSocket failures in production is that reverse proxies need to be explicitly configured to forward the HTTP \u0026ldquo;Upgrade\u0026rdquo; handshake that kicks off a WebSocket connection [15]. If that config is missing — and it very often is, because not every ops team thinks to add it — your socket just won\u0026rsquo;t connect, and now you\u0026rsquo;re stuck writing fallback logic (long-polling, retry loops, the works) just so your app degrades gracefully.\nPlain HTTP doesn\u0026rsquo;t have this problem. It is the thing every proxy, firewall, and corporate network on Earth is built to expect and allow. That\u0026rsquo;s not a small advantage — that\u0026rsquo;s \u0026ldquo;your app actually works for the accountant on the hotel Wi-Fi\u0026rdquo; levels of advantage.\nSo how do the big real-time apps actually do it? This is the part that I find genuinely instructive, because companies like Slack and Discord do lean heavily on persistent connections — but notice they don\u0026rsquo;t replace HTTP with sockets. They run both, side by side, each doing what it\u0026rsquo;s good at.\nDiscord\u0026rsquo;s Gateway is a persistent WebSocket connection that pushes real-time events — a channel got renamed, a role was created, someone went online. But Discord is explicit that in most cases, regular operations on its resources should go through the regular HTTP API, not the Gateway, because gateway connections are simply more complex to open, maintain, and recover from disconnects [13]. Slack\u0026rsquo;s Socket Mode is similar — apps use a WebSocket to receive live events, but Slack explicitly recommends still using the standard Web API (plain HTTPS) to send responses back [14]. One channel for \u0026ldquo;tell me what just happened,\u0026rdquo; another for \u0026ldquo;here\u0026rsquo;s what I want to do about it.\u0026rdquo; Notice the pattern? The persistent connection is reserved for the one thing HTTP genuinely can\u0026rsquo;t do well: the server pushing data to you on its own schedule. Everything else — logging in, fetching your message history, updating your profile, searching — still rides on plain old stateless HTTP requests, because that\u0026rsquo;s the model that caches well, scales horizontally without drama, and survives a corporate firewall.\nSo when does \u0026ldquo;one socket per page\u0026rdquo; actually make sense? I don\u0026rsquo;t want to make it sound like WebSockets are some kind of mistake — they\u0026rsquo;re not. They\u0026rsquo;re the right call when:\nThe server needs to push data without being asked — live chat, multiplayer game state, stock tickers, collaborative editing where every keystroke from one person needs to reach everyone else within milliseconds [3]. Update frequency is high enough that polling would be wasteful — if you\u0026rsquo;d otherwise be hammering an endpoint every second \u0026ldquo;just in case,\u0026rdquo; a socket is clearly the more honest design. Latency actually matters to the experience — a half-second delay in a typing indicator is fine; a half-second delay in a competitive multiplayer game is not. But for a typical page load — fetching a product page, a dashboard, a list of orders, a user\u0026rsquo;s profile — none of those conditions really apply. You\u0026rsquo;re asking for something once, getting an answer, and moving on. That\u0026rsquo;s exactly the shape HTTP was built for, and exactly the shape that benefits from caching, statelessness, and not needing your ops team to configure sticky sessions and shared Redis state just to keep things working.\nMy honest take If I had to boil this down: the \u0026ldquo;stateful\u0026rdquo; part of WebSockets isn\u0026rsquo;t a bonus feature you get for free — it\u0026rsquo;s the bill you pay for the ability to receive pushes. It\u0026rsquo;s a great deal when you actually need pushes. It\u0026rsquo;s a bad deal when you don\u0026rsquo;t, because you end up carrying all the costs (memory per connection, sticky sessions, firewall fragility, zero caching) for a feature you\u0026rsquo;re not using.\nHTTP/2\u0026rsquo;s multiplexing already gave us most of the \u0026ldquo;single efficient pipe\u0026rdquo; benefit people associate with sockets, minus the operational headache [9]. So the real answer to \u0026ldquo;why not one socket per page\u0026rdquo; isn\u0026rsquo;t \u0026ldquo;because HTTP is good and sockets are bad\u0026rdquo; — it\u0026rsquo;s that the two protocols are optimized for opposite problems, and reaching for the stateful one by default just swaps a problem you don\u0026rsquo;t have (too many requests) for several you definitely will have (memory pressure, sticky sessions, cache invalidation, and a very confused ops engineer at 2 AM).\nSources How Do WebSockets Work? — Postman Blog WebSocket vs HTTP: When to Use Each Protocol — WebSocket.org WebSocket vs REST: Key differences and which to use — Ably WebSocket Connection Limits: The Real Bottlenecks — WebSocket.org WebSockets at Scale: Architecture for Millions of Connections — WebSocket.org How to scale WebSockets for high-concurrency systems — Ably How to Scale WebSocket Connections — OneUptime Connection management in HTTP/1.x — MDN Web Docs Chrome\u0026rsquo;s 6 TCP connections limit — HTTP/1.1 WebSocket Handshake: HTTP Upgrade at Protocol Level — WebSocket.org RFC 6455 — The WebSocket Protocol Web (HTTP/S) Cache and Caching Proxy — Imperva CDN Guide Gateway Documentation — Discord Developers Comparing HTTP \u0026amp; Socket Mode — Slack Developer Docs How to Fix \u0026lsquo;Connection Refused\u0026rsquo; WebSocket Errors — OneUptime Getting through firewalls — RTC Quickstart Guide ","permalink":"https://cloudmato.com/posts/websocket-vs-http-one-socket-per-page/","title":"Why Not Just Use One WebSocket Per Page Instead of HTTP?"},{"content":"I kept hearing \u0026ldquo;use WebSockets for real-time stuff\u0026rdquo; without anyone explaining what actually happens on the wire. So I went and read the RFC, poked at a few servers, and figured I\u0026rsquo;d write down what I found — including the part that confused me the most: whether WebSocket is a protocol of its own or just some clever trick on top of HTTP.\nSo what is a WebSocket? A WebSocket is a persistent, full-duplex communication channel between a browser (or any client) and a server, opened over a single TCP connection [1]. Full-duplex means both sides can send messages whenever they want — not just in response to a request. That\u0026rsquo;s the part that breaks the usual mental model of the web.\nCompare that to how you\u0026rsquo;ve probably been thinking about web traffic your whole career:\nClient sends a request. Server sends back a response. Connection (logically) closes or sits idle until the next request. With WebSockets, once the connection is open, either side can push data at any moment — no request needed first. The server can message you the instant something happens, instead of waiting for you to ask \u0026ldquo;anything new?\u0026rdquo; [2].\nAre WebSockets a protocol? Yes — and it\u0026rsquo;s a real standard This is the bit I was genuinely unsure about. WebSocket is not a JavaScript trick or a library feature — it\u0026rsquo;s a standardized protocol, defined in RFC 6455 by the IETF in 2011 [1][3]. It has its own URI schemes too: ws:// for plain WebSocket traffic (default port 80) and wss:// for WebSocket over TLS (default port 443, the encrypted version you should actually use in production) [1].\nIn terms of where it sits in the stack: both HTTP and WebSocket are application-layer (Layer 7) protocols that run on top of TCP (Layer 4) [4]. So WebSocket isn\u0026rsquo;t replacing TCP or competing with HTTP at a fundamental level — it\u0026rsquo;s a sibling protocol to HTTP that happens to start its life by speaking HTTP.\n\u0026ldquo;The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.\u0026rdquo; — RFC 6455 [3]\nThat \u0026ldquo;opted-in\u0026rdquo; phrasing matters — a server has to explicitly agree to switch protocols. Which brings us to the handshake.\nThe handshake: how an HTTP request turns into a WebSocket This is the cleverest part of the design, and honestly the reason WebSockets work as well as they do across the existing internet. Instead of inventing a brand-new connection mechanism that firewalls, proxies and corporate networks would all need to learn about, WebSocket bootstraps itself through an ordinary HTTP request using the Upgrade header [5][6].\nHere\u0026rsquo;s the actual sequence:\nThe client sends a normal-looking HTTP GET request, but with extra headers: Upgrade: websocket, Connection: Upgrade, and a randomly generated Sec-WebSocket-Key [1][6]. The server, if it supports WebSockets, responds with HTTP/1.1 101 Switching Protocols, echoing Upgrade: websocket and Connection: Upgrade, plus a Sec-WebSocket-Accept header [6]. That Sec-WebSocket-Accept value isn\u0026rsquo;t arbitrary — the server takes the client\u0026rsquo;s key, appends a fixed \u0026ldquo;magic\u0026rdquo; GUID (258EAFA5-E914-47DA-95CA-C5AB0DC85B11), runs it through SHA-1, and base64-encodes the result. This proves the server actually understood the WebSocket request rather than some random server just echoing headers back [6]. From this point on, the same underlying TCP socket stops speaking HTTP entirely and starts exchanging WebSocket frames — a lightweight binary framing format with as little as 2-6 bytes of overhead per message, supporting text frames, binary frames, and control frames (ping/pong/close) [2][1]. Once that handshake is done, the connection is no longer \u0026ldquo;an HTTP connection that happens to do something special.\u0026rdquo; It has fully become a WebSocket connection — same TCP pipe, completely different protocol running over it.\nWebSocket vs HTTP — the actual differences Here\u0026rsquo;s where it gets interesting, because the two protocols solve fundamentally different problems even though one bootstraps the other.\nAspect HTTP WebSocket Communication model Request–response, client always initiates [2] Full-duplex — either side sends anytime [1][2] Connection lifetime Short-lived; opened and closed per exchange (or pooled/reused) [2] Persistent; stays open after the handshake until explicitly closed [2] Per-message overhead Hundreds of bytes of headers on every request/response [2] As little as 2–6 bytes of frame overhead [2] Statefulness Stateless by design — each request stands alone Stateful — the server keeps the connection (and often session context) alive [4] Server-initiated push Not natively possible; client must ask Native — server can push the moment data is ready [2] URI scheme http://, https:// ws://, wss:// [1] Underlying transport TCP (Layer 4), itself Layer 7 Also TCP (Layer 4), also Layer 7 [4] The big one is \u0026ldquo;who\u0026rsquo;s allowed to start talking.\u0026rdquo; In HTTP, the server can never just message you — it has to wait to be asked. That\u0026rsquo;s why apps that need live updates over plain HTTP resort to workarounds like polling (asking \u0026ldquo;anything new?\u0026rdquo; every few seconds) or long-polling (asking and having the server hold the request open until something happens). Both work, but they waste bandwidth on empty responses and add latency proportional to your polling interval [7].\nWebSockets remove that constraint entirely. Once the handshake completes, the server can shove a message down the pipe the instant something happens — no polling, no waiting, no wasted \u0026ldquo;nothing new\u0026rdquo; round trips [7].\nWhen you\u0026rsquo;d actually reach for WebSockets I\u0026rsquo;ll be honest — for most CRUD apps and dashboards that refresh every few seconds, plain HTTP with polling or long-polling is simpler to run and good enough [7]. WebSockets earn their complexity when the frequency and direction of updates make request-response awkward:\nChat applications — messages from any participant need to reach everyone else in near real time, and the standard approach here is WebSockets for good reason [7]. Multiplayer gaming — game state often needs to sync 20–60 times per second; nothing else gets close to the per-frame efficiency of a persistent socket [7]. Collaborative editing — every keystroke needs to propagate with minimal delay (think Google Docs-style tools). Financial trading / live price feeds — sub-100ms updates with the ability to submit orders on the same connection [7]. Live notifications and dashboards that genuinely need second-by-second pushes rather than periodic refreshes. If your \u0026ldquo;real-time\u0026rdquo; requirement is actually \u0026ldquo;update every 30 seconds is fine,\u0026rdquo; don\u0026rsquo;t reach for WebSockets just because it sounds modern. The trade-off is real — WebSockets need you to manage connection state, reconnection logic, and scaling thousands of long-lived sockets, which is a meaningfully different operational burden than stateless HTTP servers behind a load balancer [7].\nA quick note on encryption Just like HTTP has HTTPS, WebSocket has wss://, which runs the WebSocket protocol over TLS [1]. There\u0026rsquo;s no good reason to ship ws:// in production — use wss:// the same way you\u0026rsquo;d refuse to ship a login form over plain http://. The handshake still starts as an HTTPS request before upgrading, so you get the same certificate-based trust model you\u0026rsquo;re already used to.\nWrapping up WebSocket isn\u0026rsquo;t a hack bolted onto HTTP — it\u0026rsquo;s its own IETF-standardized protocol (RFC 6455) that simply uses HTTP\u0026rsquo;s Upgrade mechanism as a polite way to ask \u0026ldquo;can we switch to something better suited for this conversation?\u0026rdquo; [1][3][6]. Once the server says yes with a 101 Switching Protocols, you\u0026rsquo;re no longer in HTTP-land at all — you\u0026rsquo;re exchanging lightweight frames over a connection that stays open and lets both sides talk whenever they need to [6][2].\nWhether you actually need that is a different question than whether it sounds cool. For most things, you don\u0026rsquo;t. For chat, games, and live collaborative tools, you very much do.\nSources WebSocket Protocol: RFC 6455 Handshake, Frames \u0026amp; More — WebSocket.org WebSockets vs HTTP: Key Differences Explained — Postman Blog RFC 6455 — The WebSocket Protocol (RFC Editor) What is a WebSocket? — IP With Ease Protocol upgrade mechanism — HTTP — MDN Web Docs Writing WebSocket servers — Web APIs — MDN Web Docs Long Polling vs WebSockets: Choosing the Right Transport for Real-Time Feeds — GetStream ","permalink":"https://cloudmato.com/posts/what-are-websockets-vs-http/","title":"What Are WebSockets and How Do They Differ From HTTP?"},{"content":"Ask three people what \u0026ldquo;loop\u0026rdquo; means in AI right now and you\u0026rsquo;ll get three different answers. One will say the agent loop. Another will start talking about model collapse and feedback loops. A third will mention human-in-the-loop from some compliance meeting. They\u0026rsquo;re all correct, which is exactly why the word has become so confusing.\nI\u0026rsquo;ve been writing software for over 8 years, and I\u0026rsquo;ve watched plenty of jargon get recycled. But \u0026ldquo;loop\u0026rdquo; is special because it\u0026rsquo;s not one trend — it\u0026rsquo;s at least four different ideas that all happen to share the same word, and all of them got hot at roughly the same time. So let me untangle them.\nSo what does \u0026ldquo;loop\u0026rdquo; even mean here? A loop, in the plain programming sense, is just something that repeats until a condition is met. while not done: do_something(). That\u0026rsquo;s it. Nothing magical.\nWhat changed is what sits inside the loop. For decades the body of a loop was deterministic code you wrote by hand. Now the body of the loop is a large language model deciding, on its own, what to do next. That single swap — from fixed instructions to a model that reasons each time around — is the whole story. Everything trending right now flows from it.\nWhen people say \u0026ldquo;loop\u0026rdquo; in an AI context today, they usually mean one of these:\nThe agentic loop — an AI agent reasoning, acting, and observing in a cycle until a task is done. This is the big one. Human-in-the-loop / on-the-loop — where a person sits in that cycle to approve or supervise. Feedback loops — AI output feeding back into AI training, sometimes degrading the models (model collapse). The training loop — RLHF and reinforcement learning, where a model improves over repeated reward cycles. These get mashed together constantly. Let me take them one at a time, starting with the one driving all the hype.\nThe agentic loop: the one that\u0026rsquo;s actually trending Here\u0026rsquo;s the cleanest definition I\u0026rsquo;ve seen, and it comes from Anthropic: an agent is just \u0026ldquo;LLMs autonomously using tools in a loop.\u0026rdquo; [1] That\u0026rsquo;s it. No mysticism. Strip away the marketing and an AI agent is a model stuck in a while loop with access to tools.\nThe loop itself has four moves that show up again and again: perceive, decide, act, observe. [2] The model reads its context, picks the next action, the action runs, the result comes back, and the loop runs again. Some people call the steps \u0026ldquo;think, act, observe\u0026rdquo; instead, but it\u0026rsquo;s the same idea.\nThe key insight — and this is what separates an agent from a regular chatbot — is that the agent doesn\u0026rsquo;t try to solve the whole problem in one shot. It takes a small step, sees what happened, and adjusts. [1] A chatbot answers once and stops. An agent keeps going.\nThink about how you\u0026rsquo;d actually fix a bug. You don\u0026rsquo;t read the entire codebase and emit a perfect patch from memory. You read an error, check a file, run the thing, see a new error, check another file, run it again. Small steps, constant correction. The agentic loop is just that behaviour, automated.\nWhere the loop actually came from This isn\u0026rsquo;t a 2026 invention. The pattern traces back to a 2022 paper from Princeton and Google Research called \u0026ldquo;ReAct: Synergizing Reasoning and Acting in Language Models.\u0026rdquo; [3] The idea was almost embarrassingly simple: instead of asking a model to either reason (chain-of-thought) or act (call a tool), let it interleave both. Think a little, act a little, observe the result, think again.\nThe results were the convincing part. Models that could reason, act, observe, and reason again did meaningfully better — the ReAct paper reported a 34% improvement on the ALFWorld benchmark and around 10% on WebShop. [3] Letting the model interact with an external environment also cut down hallucinations, because reality kept pushing back on its assumptions. [4]\nSo the ReAct loop quietly became the de facto standard architecture for LLM agents. [4] Most agent frameworks you hear about today — whether they say so or not — are running some flavour of it.\nWhy it\u0026rsquo;s blowing up now and not in 2022 This is the question worth sitting with. The pattern is four years old. Why is \u0026ldquo;agentic loop\u0026rdquo; a 2026 buzzword and not a 2022 one?\nHonestly, it\u0026rsquo;s because the loop only works when the model inside it is good enough. A few things lined up:\nThe models got reliable enough to trust with multiple steps. A loop is only as good as each iteration. If the model makes a dumb decision on step 3, the error compounds through steps 4, 5, 6. Early models drifted badly. Newer ones hold a plan together long enough to be useful. Tool use became standardized. Anthropic\u0026rsquo;s Model Context Protocol (MCP) gave agents a common way to plug into external tools instead of everyone hand-rolling glue code. [1] Suddenly the \u0026ldquo;act\u0026rdquo; step had a real ecosystem behind it. Context windows grew. The loop accumulates history — every thought, action, and observation gets carried forward. That needs room. Bigger context windows made longer loops viable, and \u0026ldquo;context engineering\u0026rdquo; became its own discipline. [5] The money showed up. This is the unglamorous reason. Gartner reported a 1,445% surge in multi-agent system inquiries between Q1 2024 and Q2 2025. [6] The agentic AI market is projected to grow from $7.6 billion in 2025 to $10.8 billion in 2026. [6] When numbers move like that, every blog (including this one, I guess) starts writing about loops. There\u0026rsquo;s also a structural shift. According to the 2026 Gartner CIO survey, only about 17% of organizations have actually deployed AI agents, but more than 60% expect to within two years — the most aggressive adoption curve among the emerging tech they track. [6] So a lot of the noise is anticipation, not deployment. Worth keeping in mind before you believe the hype.\nSingle agents vs. teams of agents One more wrinkle worth understanding. The early picture was one all-purpose agent looping away on your task. The direction now is orchestrated teams of specialized agents — a researcher agent, a coder agent, a reviewer agent — each running its own loop, coordinated by an orchestrator. [6]\nThat 1,445% jump in inquiries Gartner saw? It was specifically about multi-agent systems. [6] The thinking is that one giant agent trying to do everything tends to lose the plot on long tasks, whereas smaller agents with narrow jobs stay focused. I\u0026rsquo;m a little skeptical here — coordinating multiple non-deterministic loops sounds like a debugging nightmare, and I suspect a lot of teams will discover that the hard way. But that\u0026rsquo;s the direction the field is betting on.\nAnthropic\u0026rsquo;s own advice cuts against the over-engineering instinct, for what it\u0026rsquo;s worth. Their three principles for building agents are: keep the design simple, make the agent\u0026rsquo;s planning steps transparent, and invest in good tool documentation. [1] They explicitly suggest starting with direct LLM API calls — many patterns are a few lines of code — before reaching for a heavy framework. [1] Good advice that most people ignore.\nHuman-in-the-loop vs. human-on-the-loop Now the loop that has nothing to do with autonomy and everything to do with control. Once you\u0026rsquo;ve got an agent acting on its own, the obvious question is: where does a human fit?\nTwo answers, and the distinction matters more than the cute naming suggests. [7]\nHuman-in-the-loop (HITL) Human-on-the-loop (HOTL) Human role Approves or intervenes at critical steps Monitors a dashboard, reviews flagged cases Decision control Final decisions stay with the human Agent executes; human supervises Example AI drafts the email, you click Send Agent sends emails; alerts fire only on anomalies Optimizes for Control, risk mitigation Speed, scale Best for High-risk, legal, ethical decisions High-volume work inside policy limits Human-in-the-loop means a person sits inside the cycle and the agent can\u0026rsquo;t take the final action without sign-off. [7] The AI drafts, you approve. Slower, safer, doesn\u0026rsquo;t scale well.\nHuman-on-the-loop pulls the person out of every step and puts them in a supervisory seat. [7] The agent runs, a dashboard tracks it, and alerts fire only when something looks off — unusual data access, weird API calls, output that doesn\u0026rsquo;t match quality baselines. The human reviews the flagged ones and can hit the kill switch. [7]\nThe move toward \u0026ldquo;on-the-loop\u0026rdquo; is a big part of why agentic AI is suddenly useful at scale. If a human has to approve every single action, you haven\u0026rsquo;t really automated anything — you\u0026rsquo;ve just added a queue. The whole productivity argument depends on stepping back to supervision. [7] But — and this is the uncomfortable part — that\u0026rsquo;s also exactly when things can go wrong without anyone noticing fast enough. Which brings me to the loops nobody wants.\nWhen loops go wrong: token spirals and runaway agents Here\u0026rsquo;s where it gets tricky, and where I think the hype skips over the scary part.\nA loop that doesn\u0026rsquo;t know when to stop is the oldest bug in programming. We\u0026rsquo;ve all written an infinite loop and frozen our machine. The 2026 version is worse, because the loop has a credit card attached.\nEach step in an agent loop sends the entire accumulated context back to the model. [8] By step 20 you\u0026rsquo;re paying for the same system prompt and conversation history twenty times over. People are calling this the \u0026ldquo;token spiral\u0026rdquo; — the modern infinite loop, but with a direct line to your bank account. [8]\nThe numbers floating around are genuinely alarming. One documented case had a runaway agent burn $2,847 in four hours, and another hit $12,000 in a single session before anyone caught it. [8] Agents reportedly burn up to 50x more tokens than a plain chat for the same conversation, because of all that repeated context. [9] On a simple 5-step loop the cost runs about 3.2x a one-shot call; at 50 steps the multiplier passes 30x; at 200 steps it\u0026rsquo;s over 100x. [8]\nSo if you\u0026rsquo;re building anything that loops, the guardrails aren\u0026rsquo;t optional:\nA hard max_iterations cap. Five or ten. Never let a loop run unbounded. [8] This one rule prevents most disasters. A per-run token budget that kills the run when it\u0026rsquo;s crossed. [8] Repetition detection — fingerprint each tool call and compare against a rolling window, so you catch the agent doing the same thing over and over. [8] A step-count alert that pings you if a single run blows past, say, 15 steps. [8] I find it a little funny that we spent years teaching ourselves to write terminating loops, invented agents that can reason, and immediately reintroduced the infinite loop — except now it costs real money instead of just hanging the terminal. Progress.\nFeedback loops and the model-collapse problem Completely different loop, equally important, and the one I find most fascinating because it\u0026rsquo;s slow and invisible.\nThis one isn\u0026rsquo;t about a single agent running. It\u0026rsquo;s about the whole AI ecosystem feeding on itself. Models are trained on data scraped from the web. More and more of that web is now written by AI. So the next generation of models trains partly on the previous generation\u0026rsquo;s output. That\u0026rsquo;s a feedback loop, and it can rot the models.\nThe phenomenon is called model collapse — when models trained on AI-generated data progressively lose quality and diversity, drifting away from the real-world distribution they were supposed to learn. [10] It was formally characterized in a 2023 Oxford and Cambridge study published in Nature, titled \u0026ldquo;AI models collapse when trained on recursively generated data.\u0026rdquo; [11] Over successive training cycles, the model reinforces its own errors, biases, and oversimplifications, and slowly loses its grip on the truth. [10]\nThe timing is the worrying bit. Estimates suggest that by 2026 a significant chunk of new text published online is AI-generated. [12] Models trained on web data from 2024 through 2026 are, whether anyone intends it or not, training on the outputs of GPT-4, Claude, Gemini, and friends — which were themselves trained on earlier human web data. [12] It\u0026rsquo;s a photocopy of a photocopy of a photocopy. Each pass loses a little fidelity.\nIn low-stakes settings this just means blander, more generic output. In healthcare, finance, or security it could mean degraded models making genuinely dangerous calls — a misdiagnosis, a bad risk score, a missed anomaly. [12] This is why \u0026ldquo;human-validated data\u0026rdquo; and provenance tracking have quietly become valuable again. Real human-written content is, ironically, becoming a scarce resource.\nThe original loop: how models learn in the first place I\u0026rsquo;ll keep this one short because it predates the current hype, but it deserves a mention because it\u0026rsquo;s also a loop and people conflate it with the rest.\nBefore a model can sit in an agent loop, it gets shaped by a training loop. The famous one is RLHF — Reinforcement Learning from Human Feedback, the technique that made models like ChatGPT actually pleasant to talk to. [13]\nThe loop works like this: a reward model (basically an AI judge trained on human preferences) scores the main model\u0026rsquo;s responses, and that score becomes a reward signal used to nudge the model toward outputs humans prefer. [13] Generate, evaluate, optimize, repeat. The model literally learns by looping over its own attempts and getting graded. [13]\nSo you\u0026rsquo;ve got loops all the way down — a training loop that shapes the model, and then an agent loop where that shaped model goes to work. Same word, very different timescales: one happens over weeks in a data center, the other over seconds on your task.\nSorting out which loop someone means If you take one thing from all this, let it be that \u0026ldquo;loop\u0026rdquo; in AI is an overloaded word, and the speaker almost never specifies which one. Here\u0026rsquo;s my quick cheat sheet:\nLoop What repeats Timescale Why it\u0026rsquo;s trending Agentic loop Reason → act → observe Seconds to minutes Models finally good enough to trust across steps Human-in/on-the-loop Human approval or supervision Per action / continuous Needed to deploy agents safely at scale Feedback loop AI output → AI training data Months to years Web is filling with AI text; model collapse risk Training loop (RLHF) Generate → reward → optimize Weeks The foundation that made all of the above usable The next time someone drops \u0026ldquo;loop\u0026rdquo; in a meeting, that\u0026rsquo;s the real question: which one? They\u0026rsquo;re related — they all describe something repeating with a model in the middle — but the engineering, the risks, and the fixes are completely different.\nThe agentic loop is the one carrying the hype, and it\u0026rsquo;s genuinely a big deal. But it only works because of the training loop underneath it, it only deploys safely with a human on the loop watching it, and the whole ecosystem quietly risks rotting from the feedback loop nobody\u0026rsquo;s policing. Four loops, one word, all tangled together. No wonder it\u0026rsquo;s confusing.\nEnd\nSources Building effective agents — Anthropic The Agent Loop, Explained: Perceive, Decide, Act, Observe ReAct: Synergizing Reasoning and Acting in Language Models (PDF) What is a ReAct Agent? — IBM Effective context engineering for AI agents — Anthropic 7 Agentic AI Trends to Watch in 2026 — MachineLearningMastery Human-in-the-Loop vs Human-on-the-Loop in Agentic AI — TekLeaders Preventing Runaway AI Agent Costs and Token Spirals — n1n.ai AI Agents Burn 50x More Tokens Than Chats — LeanOps What Is Model Collapse? — IBM AI models collapse when trained on recursively generated data — Nature The AI feedback loop: Researchers warn of \u0026lsquo;model collapse\u0026rsquo; — VentureBeat Reinforcement Learning from Human Feedback (RLHF) for LLMs — SuperAnnotate ","permalink":"https://cloudmato.com/posts/loops-in-ai-explained/","title":"Loops in AI: What They Are and Why Everyone's Talking"},{"content":"Every request you make to a website carries a small pile of metadata you never see. Headers. They decide whether your connection is encrypted, whether a page can be embedded in an iframe, which CDN edge served you, and whether the browser should remember a cookie for a year. I wanted to see what a real, busy production site sends, so I pointed curl at an Amazon endpoint and dumped the response headers. Turns out there\u0026rsquo;s a lot to unpack.\nWhat I actually ran I made a plain GET request to one of Amazon India\u0026rsquo;s internal \u0026ldquo;add to cart\u0026rdquo; config endpoints and asked curl to print only the response headers:\ncurl -s -D - -o /dev/null \\ \u0026#34;https://www.amazon.in/cart/add-to-cart/patc-config?clientName=SiteWideActionExecutor\u0026amp;ref_=ax_patc_cfg\u0026#34; \\ -A \u0026#34;Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0\u0026#34; The -D - dumps headers to stdout, and -o /dev/null throws away the body (I only care about headers here). Here\u0026rsquo;s roughly what came back:\nHTTP/2 200 content-type: application/json;charset=UTF-8 date: Sun, 07 Jun 2026 18:55:58 GMT server: Server x-amz-rid: BE1Q4YXSQ0DKN83F4JYQ set-cookie: session-id=259-8655047-0491149; Domain=.amazon.in; Expires=...; Path=/; Secure set-cookie: session-id-time=2082787201l; Domain=.amazon.in; Expires=...; Path=/; Secure set-cookie: i18n-prefs=INR; Domain=.amazon.in; Expires=...; Path=/ set-cookie: lc-acbin=en_IN; Domain=.amazon.in; Expires=...; Path=/ x-content-type-options: nosniff x-xss-protection: 1; accept-ch: ect,rtt,downlink,device-memory,...,sec-ch-ua-platform-version accept-ch-lifetime: 86400 content-security-policy-report-only: default-src \u0026#39;self\u0026#39; blob: https: data: ...;report-uri https://metrics.media-amazon.com/ content-encoding: gzip content-security-policy: upgrade-insecure-requests;report-uri https://metrics.media-amazon.com/ strict-transport-security: max-age=47474747; includeSubDomains; preload vary: X-Amazon-Wtm-Tag-...,Accept-Encoding,User-Agent x-frame-options: SAMEORIGIN x-cache: Miss from cloudfront via: 1.1 db8e720c1e186c4a9d38db72ecaa0492.cloudfront.net (CloudFront) x-amz-cf-pop: BOM78-P2 alt-svc: h3=\u0026#34;:443\u0026#34;; ma=86400 x-amz-cf-id: 4UqtO1tdgZlbHdAUl2W47T2W4MGf7vb-mx2Y-KEevkHqCcCyhE_b0Q== That\u0026rsquo;s a surprisingly dense response for what\u0026rsquo;s basically a config endpoint. Let me walk through them in groups, because some of these matter a lot more than others.\nThe boring-but-essential basics content-type content-type: application/json;charset=UTF-8 tells the client what kind of data the body holds and how to decode it. Sounds trivial, but it\u0026rsquo;s load-bearing. A browser uses this to decide whether to parse the response as JSON, render it as HTML, or treat it as a download. The charset=UTF-8 part specifies the character encoding so accented characters and non-Latin scripts come through correctly.\nHere\u0026rsquo;s the thing — if a server lies or stays vague about content type, browsers used to \u0026ldquo;sniff\u0026rdquo; the body and guess. That guessing is exactly what x-content-type-options: nosniff (more on that below) exists to shut down.\ndate and server date is just the server\u0026rsquo;s timestamp when it generated the response. Caches use it for freshness math.\nserver: Server is my favourite bit of dry humour in this whole dump. Most servers proudly announce themselves — Server: nginx/1.24.0 or Server: Apache/2.4. Amazon\u0026rsquo;s frontend instead returns the literal word Server. That\u0026rsquo;s deliberate obfuscation. Why hand an attacker a free version-number lookup against known CVEs? So they strip it down to something useless. Small thing, but it tells you someone thought about it.\ncontent-encoding content-encoding: gzip means the body was compressed with gzip before being sent. Your client transparently decompresses it. This is why the response is smaller on the wire than the actual JSON. Nothing exotic, but it pairs directly with the Vary header later — and that pairing is where people trip up.\nx-amz-rid x-amz-rid: BE1Q4YXSQ0DKN83F4JYQ is an Amazon-specific request ID. It\u0026rsquo;s an internal correlation token. If something breaks and you file a support ticket, this is the string their engineers grep their logs for. You\u0026rsquo;ll see a similar idea repeated with the CloudFront headers at the end — modern infra is obsessed with being able to trace one specific request through a dozen systems.\nThe cookies: four Set-Cookie headers Amazon sets four cookies in this single response. The Set-Cookie header is how a server asks the browser to store a value and send it back on future requests. Each attribute after the value tunes how the cookie behaves:\nAttribute What it does Domain=.amazon.in Cookie is sent to amazon.in and all its subdomains Expires=...2027... Persistent cookie — survives browser restarts until that date Path=/ Sent for every path on the site Secure Only sent over HTTPS, never plain HTTP The two interesting ones:\nsession-id and session-id-time both carry the Secure flag. Per MDN, a Secure cookie is only attached to encrypted HTTPS requests, which blocks an on-path attacker from sniffing it off an accidental HTTP request. For a session identifier — the thing that effectively is your logged-in state — that\u0026rsquo;s the bare minimum you\u0026rsquo;d want. i18n-prefs=INR and lc-acbin=en_IN are preference cookies: currency in Rupees, locale English-India. Notice these two don\u0026rsquo;t have Secure. That\u0026rsquo;s a reasonable call — knowing someone prefers INR isn\u0026rsquo;t sensitive, and it\u0026rsquo;s not a hijacking risk. One nuance worth calling out. The Domain=.amazon.in with a leading dot makes these cookies available across every subdomain — www.amazon.in, sellercentral.amazon.in, and so on. That\u0026rsquo;s intentional for a service that spans many subdomains, but it\u0026rsquo;s also the kind of decision you want to make consciously, because a cookie scoped too broadly is a bigger blast radius if anything leaks. The RFC 6265 spec governs all of this behaviour if you want the formal version.\nNotably absent here: HttpOnly and SameSite. Those usually show up on the more sensitive auth cookies set during actual login, not on a config endpoint like this one. So don\u0026rsquo;t read too much into their absence in this particular response.\nThe security headers — the interesting part This is where the response gets genuinely worth studying, because Amazon ships a stack of defensive headers and a couple of them have history.\nstrict-transport-security strict-transport-security: max-age=47474747; includeSubDomains; preload HSTS tells the browser: \u0026ldquo;From now on, only ever talk to me over HTTPS. If anyone hands you an http:// link for me, upgrade it to HTTPS before the request even leaves the machine.\u0026rdquo; Three directives here:\nmax-age=47474747 — remember this rule for ~550 days (that number is just 47474747 seconds; someone clearly enjoyed the repeating digits). includeSubDomains — the rule covers every subdomain too. preload — Amazon wants to be baked into browsers\u0026rsquo; hardcoded HSTS lists, so even your very first visit is forced to HTTPS, with no insecure first request. Why does this matter so much? Without HSTS, your first request to amazon.in might go out as plain HTTP and get hijacked before the redirect to HTTPS — the classic SSL-stripping attack. The OWASP HSTS cheat sheet explains this risk well. One catch: browsers ignore an HSTS header if it arrives over plain HTTP, specifically so a man-in-the-middle can\u0026rsquo;t inject a bogus one. It only counts when it comes over a connection that\u0026rsquo;s already secure.\nTwo Content-Security-Policy headers This is the bit I found most telling. Amazon sends both an enforcing policy and a report-only policy:\ncontent-security-policy: upgrade-insecure-requests;report-uri https://metrics.media-amazon.com/ content-security-policy-report-only: default-src \u0026#39;self\u0026#39; blob: https: data: ...;report-uri https://metrics.media-amazon.com/ The enforcing one is deliberately tiny. upgrade-insecure-requests tells the browser to rewrite every http:// sub-resource (images, scripts, whatever) to https:// before fetching. On a site this old with this many legacy URLs floating around, manually fixing each one would be madness — so they let the browser upgrade them in bulk.\nThe second header is the clever part. Content-Security-Policy-Report-Only blocks nothing. It runs a stricter trial policy (default-src 'self' ...) and, whenever something would have been blocked, POSTs a JSON violation report to the report-uri. This is how you roll out a tight CSP without breaking the live site:\nDeploy the strict policy in report-only mode. Watch the violation reports roll into your metrics endpoint. Fix the legitimate things that trip it. Once it\u0026rsquo;s quiet, promote it to the enforcing header. It\u0026rsquo;s basically running the strict policy in a wind tunnel before strapping it to the actual plane. The report-uri directive is technically deprecated in favour of report-to, but it\u0026rsquo;s kept around for the many browsers still relying on it.\nx-content-type-options: nosniff Short header, real teeth. nosniff tells the browser to trust the Content-Type and never guess. Without it, a browser might look at a file served as text/plain, decide it \u0026ldquo;looks like\u0026rdquo; HTML or JavaScript, and execute it — a MIME-confusion attack. If an attacker can upload a file that gets served back, content sniffing is how a harmless-looking upload turns into running script. nosniff slams that door.\nx-frame-options: SAMEORIGIN X-Frame-Options: SAMEORIGIN controls who\u0026rsquo;s allowed to load this page inside a \u0026lt;frame\u0026gt; or \u0026lt;iframe\u0026gt;. SAMEORIGIN means only Amazon pages can frame other Amazon pages — evil-site.com can\u0026rsquo;t. This is the core defence against clickjacking, where an attacker invisibly overlays your real page and tricks you into clicking \u0026ldquo;Buy now\u0026rdquo; or \u0026ldquo;Confirm\u0026rdquo; while you think you\u0026rsquo;re clicking something else. For a shopping cart endpoint, you can see exactly why they care.\nx-xss-protection: 1 Now, x-xss-protection: 1 is the odd one out, and honestly it\u0026rsquo;s a bit of a relic. It was a feature of old Internet Explorer, Chrome and Safari that tried to detect reflected XSS and block the page. The catch — and security folks have been blunt about this — is that the filter itself could be abused to introduce vulnerabilities into otherwise-safe pages. Modern guidance is to either turn it off (0) or just not send it, and lean on a real Content-Security-Policy instead. Amazon still sends 1, probably for the long tail of ancient browsers. I wouldn\u0026rsquo;t copy this one into a new project.\nPerformance and protocol headers accept-ch and accept-ch-lifetime accept-ch: ect,rtt,downlink,device-memory,...,sec-ch-ua-platform-version accept-ch-lifetime: 86400 This is Client Hints. With Accept-CH, the server is saying: \u0026ldquo;On your next requests, please also tell me your network speed (ect, rtt, downlink), how much device memory you have (device-memory), and some user-agent details.\u0026rdquo; Amazon can then tailor what it serves — lighter images on a slow 2g connection, for instance.\naccept-ch-lifetime: 86400 says remember this preference for a day. Worth knowing: Accept-CH-Lifetime is no longer recommended and largely removed from the spec, but plenty of sites still send it for compatibility. Client Hints only work over a secure connection, and only get sent to the first-party origin — a privacy guardrail so random third parties can\u0026rsquo;t quietly fingerprint your device.\nvary vary: X-Amazon-Wtm-Tag-...,Accept-Encoding,User-Agent The Vary header is the one that quietly breaks caches when people forget it. It tells every cache between Amazon and you: \u0026ldquo;This response depends on these request headers, so key your cached copies on them.\u0026rdquo;\nAccept-Encoding — remember the gzip body and the Brotli body separately. Without this, a cache might hand a gzip blob to a client that asked for something else. Since the response is gzip-encoded, this entry is doing real work. User-Agent — the response can differ between a mobile and a desktop browser, so cache them apart. A small caution the experts keep repeating: Vary: User-Agent is a blunt instrument. There are effectively infinite User-Agent strings out there, so this can shred your cache hit rate by storing a near-unique copy per browser version. Big sites with their own CDN logic can afford it; on a small site I\u0026rsquo;d think twice.\nalt-svc alt-svc: h3=\u0026#34;:443\u0026#34;; ma=86400 Alt-Svc is the handshake that bootstraps HTTP/3. This request came back over HTTP/2, but this header is Amazon whispering: \u0026ldquo;Hey, I also speak h3 (HTTP/3 over QUIC) on port 443 — feel free to use it for the next 86400 seconds.\u0026rdquo; A capable browser will quietly try HTTP/3 in the background and, if it connects, switch over for faster, lower-latency requests. As bagder\u0026rsquo;s HTTP/3 guide puts it, this is how the upgrade happens without breaking anything for clients that don\u0026rsquo;t support it.\nThe CloudFront trail The last cluster tells you Amazon is serving this through its own CDN, CloudFront:\nHeader Value Meaning x-cache Miss from cloudfront Not in edge cache — fetched from origin via 1.1 ...cloudfront.net (CloudFront) A CloudFront proxy handled the request x-amz-cf-pop BOM78-P2 Served from the Mumbai (BOM) edge location x-amz-cf-id 4UqtO1... Opaque trace ID for this exact request A few things click into place here:\nx-cache: Miss makes sense — a personalised cart-config endpoint shouldn\u0026rsquo;t be cached and served to everyone, so a miss is the correct behaviour, not a problem. x-amz-cf-pop: BOM78 uses IATA airport codes. BOM is Mumbai. I\u0026rsquo;m in India, so CloudFront routed me to the nearest edge — exactly what a CDN is supposed to do. The -P2 suffix indicates the cache tier. x-amz-cf-id is the CloudFront sibling of x-amz-rid from earlier — an encrypted, opaque token that means nothing to me but lets AWS Support pull this precise request out of their logs. Notice the pattern: every layer stamps its own trace ID. That redundancy is how you debug a request that passed through the CDN, the frontend, and the backend without losing the thread. So what\u0026rsquo;s the takeaway? For one throwaway config request, Amazon sent back encryption enforcement, three flavours of XSS/clickjacking defence, two CSP strategies running side by side, cookie scoping, client-hint negotiation, HTTP/3 advertising, and a full CDN trace trail. None of it is exotic — every header here is documented on MDN and most are things you can switch on yourself. The difference is that Amazon turns on all of them, defence-in-depth style, and even ships a report-only CSP just to test the next tightening before it\u0026rsquo;s enforced.\nIf you run anything in production, this dump is a decent checklist. HSTS with preload, nosniff, a real CSP, X-Frame-Options, Secure cookies, and a sane Vary will get you most of the way. And maybe — like Amazon — set your Server header to something gloriously unhelpful while you\u0026rsquo;re at it.\nSources Strict-Transport-Security header - MDN Web Docs HTTP Strict Transport Security - OWASP Cheat Sheet Content-Security-Policy: upgrade-insecure-requests - MDN Content Security Policy (CSP) Guide - MDN Content-Security-Policy: report-uri directive - MDN HTTP Headers Cheat Sheet - OWASP X-Content-Type-Options HTTP Header - KeyCDN Accept-CH header - MDN Web Docs Set-Cookie header - MDN Web Docs RFC 6265 - HTTP State Management Mechanism Best practices for using the Vary header - Fastly Understanding The Vary Header - Smashing Magazine Alt-Svc header - MDN Web Docs Bootstrap with Alt-Svc - HTTP/3 explained X-Amz-Cf-Pop - http.dev X-Amz-Cf-Id - http.dev Amazon CloudFront Response Headers Policies - AWS ","permalink":"https://cloudmato.com/posts/understanding-common-http-headers-on-amazon/","title":"Understanding Common HTTP Headers on Amazon"},{"content":"Everyone slaps \u0026ldquo;RESTful\u0026rdquo; on their API. Open any docs page, scroll the marketing copy, and there it is — \u0026ldquo;our clean, RESTful API.\u0026rdquo; But here\u0026rsquo;s the uncomfortable bit: by the strict definition, almost none of them actually are. So the question you\u0026rsquo;re really asking is whether the word still means anything if you break some of the rules. Honestly, that\u0026rsquo;s where it gets tricky.\nShort answer first, because I hate articles that bury it: yes, you can still call it RESTful in everyday conversation, but no, it isn\u0026rsquo;t a REST API by Roy Fielding\u0026rsquo;s original definition unless it\u0026rsquo;s hypertext-driven. Both of those things are true at the same time, and the gap between them is the whole story.\nWhat \u0026ldquo;REST\u0026rdquo; actually means (and who decided) REST isn\u0026rsquo;t a protocol. It\u0026rsquo;s not a spec you download. It\u0026rsquo;s an architectural style described by Roy Fielding in his 2000 PhD dissertation, where he was basically reverse-engineering the principles that made the web itself scale [1]. That\u0026rsquo;s an important framing — REST came after the web worked, as an explanation of why it worked.\nFielding defined six constraints. Hit all of them and you\u0026rsquo;ve got a RESTful system; skip the wrong one and, by his definition, you don\u0026rsquo;t [2][3]:\nClient–server — separate the UI concerns from the data storage concerns so each can evolve on its own. Stateless — every request carries everything the server needs. The server keeps nothing about your previous request lying around. Cacheable — responses must say whether they can be cached, so clients and intermediaries can reuse them. Uniform interface — the big one, and the one everyone half-implements. More on this below. Layered system — a client can\u0026rsquo;t tell whether it\u0026rsquo;s talking to the real server or a proxy/load balancer/gateway in front of it. Code on demand — optional. The server can ship executable code (think JavaScript) to extend the client. This is the only constraint Fielding marked optional. Notice that last word: optional. Code on demand is the only one you\u0026rsquo;re explicitly allowed to drop. Everything else, in the strict reading, is mandatory. So when people ask \u0026ldquo;can I skip a rule and still be RESTful,\u0026rdquo; the honest answer is the rules already have a built-in opt-out for exactly one of them, and it\u0026rsquo;s not the one you wanted to skip.\nThe uniform interface is where APIs quietly fall apart The uniform interface itself breaks into four sub-constraints, and this is where the \u0026ldquo;RESTful\u0026rdquo; label gets shaky [3]:\nIdentification of resources — each thing has a URI, like /users/123. Manipulation through representations — you hold a representation (JSON, XML) and that\u0026rsquo;s enough to modify or delete the resource. Self-descriptive messages — each message carries enough info to process it (media types, status codes, headers). HATEOAS — Hypermedia As The Engine Of Application State. The server\u0026rsquo;s responses include links telling the client what it can do next. Most APIs nail the first three. They have nice /users/123 URLs, they speak JSON, they set content types and status codes. And then they completely ignore number four. That\u0026rsquo;s not a small omission — it\u0026rsquo;s the one Fielding cared about most.\nThe line in the sand: \u0026ldquo;REST APIs must be hypertext-driven\u0026rdquo; In 2008, eight years after the dissertation, Fielding got fed up enough to write a blog post with a title that doesn\u0026rsquo;t leave much room for debate: \u0026ldquo;REST APIs must be hypertext-driven\u0026rdquo; [1]. His exact words:\n\u0026ldquo;If the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API.\u0026rdquo;\nThat\u0026rsquo;s blunt. He was reacting to a flood of APIs calling themselves REST while actually being RPC-style calls dressed in HTTP clothing [11]. His point: a true REST client should start at one entry URL and discover everything else by following links the server hands it — exactly like you browse a website without memorising every URL on it. The data format and link relations tell the client what\u0026rsquo;s possible, not some out-of-band documentation you read on a wiki.\nThink about how you use a normal website. You don\u0026rsquo;t memorise that the checkout page lives at /cart/checkout/step-3. You click a button that\u0026rsquo;s on the page. The page tells you what you can do next. That\u0026rsquo;s hypertext driving the application state. Fielding\u0026rsquo;s argument is that a REST API should work the same way for machines [1].\nBy that standard, here\u0026rsquo;s the gut punch: the Twitter and Facebook APIs everyone learned \u0026ldquo;REST\u0026rdquo; from don\u0026rsquo;t use hypermedia at all [8]. Neither do most of the APIs in your daily work. In practice, most so-called REST APIs implement statelessness and a uniform interface but ignore HATEOAS entirely [7][8].\nSo is your API RESTful? Meet the Richardson Maturity Model Leonard Richardson came up with a model that Martin Fowler popularised, and it\u0026rsquo;s the most useful tool for answering your question because it stops treating \u0026ldquo;RESTful\u0026rdquo; as a yes/no and turns it into a ladder [4][5]. There are four levels, 0 through 3 [13].\nLevel Name What it uses What it\u0026rsquo;s missing 0 The Swamp of POX Single URI, single verb (usually POST) Everything. This is RPC/SOAP-style. 1 Resources Multiple URIs, one per resource HTTP verbs still misused 2 HTTP Verbs URIs + proper GET/POST/PUT/DELETE + status codes HATEOAS 3 Hypermedia Everything above + HATEOAS links Nothing — this is \u0026ldquo;full\u0026rdquo; REST Here\u0026rsquo;s the reality check that should make you feel better: the vast majority of APIs that proudly call themselves RESTful are sitting at Level 2 [6][8]. They\u0026rsquo;ve got clean resource URLs, they use the right HTTP methods, they return sensible status codes. They just don\u0026rsquo;t embed navigational links. And Level 2 is genuinely good engineering — it\u0026rsquo;s a perfectly sane place to be.\nFowler himself is careful here. He notes the model is a helpful way to think about the elements of REST, not a strict definition of when you\u0026rsquo;re allowed to use the word [4]. Fielding, on the other hand, would say only Level 3 earns the name. Both views exist in the wild, and that tension is exactly why your question doesn\u0026rsquo;t have a clean answer.\nHTTP API vs REST API — they\u0026rsquo;re not the same thing This trips up a lot of people, so let me state it plainly. All REST APIs run over HTTP, but not every HTTP API is a REST API [10]. The web API category is huge — anything you can call over HTTP qualifies. REST is a specific architectural style inside that category with those six constraints [8].\nWhen your API is structured around resources, uses verbs correctly, and returns JSON, but doesn\u0026rsquo;t do hypermedia, what you actually have is an HTTP API that follows REST conventions [10]. Some people call it \u0026ldquo;REST-like,\u0026rdquo; some call it \u0026ldquo;pragmatic REST,\u0026rdquo; and some just keep calling it RESTful because that\u0026rsquo;s the word everyone understands [9]. None of those are wrong in casual use. They\u0026rsquo;re just more honest about where you sit on the ladder.\nBen Morris has a solid piece on pragmatic REST arguing that dropping hypermedia is a deliberate, reasonable trade-off for most teams, not a failure [9]. I\u0026rsquo;m inclined to agree, and I\u0026rsquo;ll get into why.\nWhy almost nobody does full HATEOAS If Level 3 is the \u0026ldquo;real\u0026rdquo; REST, why does basically the entire industry stop at Level 2? A few honest reasons:\nClients don\u0026rsquo;t actually navigate dynamically. The dream of HATEOAS is a generic client that discovers the API at runtime by following links. In reality, your frontend team hardcodes the endpoints they need against a known contract. The links sit there unused. It\u0026rsquo;s more work for unclear payoff. Embedding _links everywhere, picking a hypermedia format like HAL, keeping relation types consistent — that\u0026rsquo;s real effort [8]. If no consumer uses the links to drive behaviour, you\u0026rsquo;ve added weight for nothing. Documentation and tooling won. OpenAPI/Swagger gives clients discoverability through a spec file instead of through runtime links. It\u0026rsquo;s not what Fielding had in mind, but it solved the practical problem people had. The big players never did it. When the most-copied APIs on earth skip hypermedia [8], that becomes the de facto standard regardless of what the dissertation says. There\u0026rsquo;s a real counterpoint though, and I don\u0026rsquo;t want to be unfair to it. APIs do exist at Level 3, and some big ones lean that way — GitHub\u0026rsquo;s API famously embeds URLs to related resources right in its responses so you can follow them rather than constructing URLs yourself. When you genuinely have many independent clients you don\u0026rsquo;t control, or a long-lived API where you want to move resources around without breaking everyone, hypermedia earns its keep. The decoupling Fielding talked about isn\u0026rsquo;t academic — it\u0026rsquo;s how the server keeps the freedom to change URLs without a coordinated client redeploy [1][12]. Most internal APIs just never need that freedom badly enough to pay for it.\nA concrete look: same endpoint, three levels Let me make this less abstract. Say you\u0026rsquo;re fetching an order. Here\u0026rsquo;s roughly what each maturity level looks like.\nLevel 0 — the swamp. One endpoint, POST everything, verbs live in the body:\nPOST /api { \u0026#34;action\u0026#34;: \u0026#34;getOrder\u0026#34;, \u0026#34;id\u0026#34;: 42 } Level 2 — where most \u0026ldquo;RESTful\u0026rdquo; APIs live. Real resource URL, real verb, real status code:\nGET /orders/42 200 OK { \u0026#34;id\u0026#34;: 42, \u0026#34;status\u0026#34;: \u0026#34;shipped\u0026#34;, \u0026#34;customerId\u0026#34;: 7 } Level 3 — hypertext-driven. Same data, but the response tells the client what it can do next:\nGET /orders/42 200 OK { \u0026#34;id\u0026#34;: 42, \u0026#34;status\u0026#34;: \u0026#34;shipped\u0026#34;, \u0026#34;_links\u0026#34;: { \u0026#34;self\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/orders/42\u0026#34; }, \u0026#34;customer\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/customers/7\u0026#34; }, \u0026#34;cancel\u0026#34;: { \u0026#34;href\u0026#34;: \u0026#34;/orders/42/cancel\u0026#34; } } } See the difference? At Level 3, a client doesn\u0026rsquo;t need to know that cancelling lives at /orders/42/cancel. The server told it. And crucially, if the order is already shipped, the server can just omit the cancel link — now the available actions are driven by state, which is the literal meaning of \u0026ldquo;hypermedia as the engine of application state\u0026rdquo; [1][8]. That\u0026rsquo;s the genuinely clever part people miss. It\u0026rsquo;s not about decorating responses with links. It\u0026rsquo;s about the server controlling what\u0026rsquo;s possible next.\nSo what should you actually call your API? Here\u0026rsquo;s my opinionated take after years of building these things.\nIf it\u0026rsquo;s Level 2, calling it \u0026ldquo;RESTful\u0026rdquo; in casual writing is fine. Everyone understands what you mean, and fighting the entire industry\u0026rsquo;s usage is a losing battle. The word has drifted, and that\u0026rsquo;s okay. If you want to be technically precise, say \u0026ldquo;REST-like\u0026rdquo; or \u0026ldquo;HTTP API following REST conventions.\u0026rdquo; Pedants on Hacker News will still argue, but you\u0026rsquo;ll be correct [7]. Reserve \u0026ldquo;REST API\u0026rdquo; with a straight face for Level 3. If someone\u0026rsquo;s being strict and asks \u0026ldquo;is it a REST API,\u0026rdquo; and you don\u0026rsquo;t do hypermedia, the honest answer is \u0026ldquo;no, it\u0026rsquo;s REST-like\u0026rdquo; [1][7]. The thing I\u0026rsquo;d push back on hardest is pretending the rules don\u0026rsquo;t exist. Plenty of teams call something RESTful without knowing there\u0026rsquo;s a HATEOAS constraint they\u0026rsquo;re skipping. Skipping it on purpose is a fine engineering decision. Skipping it because you never knew it existed is just ignorance with good marketing.\nThe rules you really shouldn\u0026rsquo;t break Not all constraints are equal, and this is where I\u0026rsquo;ll be direct about what actually matters for a working API versus what\u0026rsquo;s safe to drop.\nStatelessness — don\u0026rsquo;t break this. The moment your server starts remembering client state between requests, you lose horizontal scaling, you lose the layered-system benefits, and you\u0026rsquo;ve genuinely left REST behind in a way that hurts you operationally [2][12]. This one has teeth. Proper use of HTTP verbs and status codes — don\u0026rsquo;t break this either. A GET that mutates data is a real bug waiting to happen, because caches and crawlers assume GET is safe. This is Level 1-to-2 stuff and it\u0026rsquo;s table stakes. Cacheability — respect it. Setting proper cache headers is low effort and it\u0026rsquo;s one of the things that made the web scale. Ignoring it is leaving performance on the floor [3]. HATEOAS — fine to skip, knowingly. This is the one almost everyone drops, and for most APIs that\u0026rsquo;s a defensible trade-off [9]. Just don\u0026rsquo;t claim full REST while you do. Notice the pattern? The constraints that protect operational properties — statelessness, caching, correct verbs — are the ones you actually feel pain from breaking. The constraint that\u0026rsquo;s purely about evolvability and discoverability — HATEOAS — is the one you can usually live without. That\u0026rsquo;s not a coincidence. It\u0026rsquo;s why the industry settled where it did.\nWhere this leaves you The word \u0026ldquo;RESTful\u0026rdquo; stopped being a binary a long time ago. Fielding has a strict definition that says hypertext or bust [1], and a good chunk of the developer world quietly ignores it because Level 2 solves their problems just fine [7][8]. Neither side is lying — they\u0026rsquo;re using the same word for two different bars.\nSo can you still call your API RESTful if it doesn\u0026rsquo;t follow all the rules? In practice, yes, and you\u0026rsquo;ll be in the overwhelming majority. Just know which rule you\u0026rsquo;re skipping, know that a purist would call you out, and know that the one you\u0026rsquo;re probably skipping — HATEOAS — is the exact one Roy Fielding said you can\u0026rsquo;t skip. Whether that bothers you depends entirely on whether you\u0026rsquo;re writing for a strict architecture review or shipping software people actually use.\nI know which side I\u0026rsquo;m usually on. But I at least know what I\u0026rsquo;m skipping.\nEnd\nSources REST APIs must be hypertext-driven — Roy T. Fielding The Six Constraints — REST API Tutorial REST Architectural Constraints — restfulapi.net Richardson Maturity Model — Martin Fowler Richardson Maturity Model — restfulapi.net Know how RESTful your API is: An Overview of the Richardson Maturity Model — Red Hat Developer Most RESTful APIs aren\u0026rsquo;t really RESTful — Florian Krämer When Is an API Truly REST vs REST-Like? — Nordic APIs Pragmatic REST: APIs without hypermedia and HATEOAS — Ben Morris REST over HTTP, or why your HTTP API isn\u0026rsquo;t RESTful — Christophe Maillard REST APIs Must be Hypertext-driven — Stefan Tilkov, INNOQ REST — Wikipedia Richardson Maturity Model — Wikipedia Roy Fielding on Versioning, Hypermedia, and REST — InfoQ ","permalink":"https://cloudmato.com/posts/can-i-call-my-api-restful/","title":"Can You Still Call an API RESTful Without Every Rule?"},{"content":"Run git log on any project that\u0026rsquo;s more than a year old and you\u0026rsquo;ll find the truth about a team. Half the messages say \u0026ldquo;fix\u0026rdquo;, \u0026ldquo;update\u0026rdquo;, \u0026ldquo;wip\u0026rdquo;, \u0026ldquo;asdf\u0026rdquo;, or my personal favourite — \u0026ldquo;stuff\u0026rdquo;. And then one day production breaks, you run git blame on the offending line, and the commit that introduced it just says \u0026ldquo;minor changes\u0026rdquo;. Cool. Very helpful. Thanks, past me.\nI\u0026rsquo;ve been writing code for over a decade and I\u0026rsquo;ll be honest: for the first few years my commit messages were garbage. It wasn\u0026rsquo;t until I had to debug someone else\u0026rsquo;s six-month-old code (and then realised the someone else was me) that the penny dropped. A diff tells you what changed. Only the commit message can tell you why. That\u0026rsquo;s the whole game.\nWhy bother? Nobody reads them anyway That\u0026rsquo;s the lie we tell ourselves. People absolutely read commit messages — just not at the moment you write them. They read them later, under pressure, when something is on fire.\nHere\u0026rsquo;s where a good message actually earns its keep:\ngit blame — someone points at a weird line of code and the commit explains the reasoning so you don\u0026rsquo;t \u0026ldquo;fix\u0026rdquo; a deliberate workaround. git log during onboarding — a new dev reads the history to understand how a feature evolved. git bisect — when you\u0026rsquo;re hunting a regression, small well-described commits let you find the culprit in minutes instead of hours. git revert — when you need to undo a change cleanly without dragging unrelated stuff with it. Release notes and changelogs — increasingly auto-generated straight from commits. A diff shows you the what. The commit message is the only place the why survives [1]. Code can explain itself; intent cannot. Six months from now nobody remembers that you removed that retry loop because the upstream API started double-charging customers. Unless you wrote it down.\nSo no, this isn\u0026rsquo;t bureaucracy. It\u0026rsquo;s leaving a trail of breadcrumbs for the person who has to maintain this — who is, statistically, going to be you.\nThe seven rules everyone quotes (and should) Most modern advice traces back to two people. Tim Pope popularised the 50/72 formatting convention in a 2008 blog post [2], and Chris Beams later wrote them up as \u0026ldquo;the seven rules of a great commit message\u0026rdquo; on cbea.ms, which has basically become the canonical reference [1]. Here they are, and they\u0026rsquo;re worth memorising:\nSeparate the subject from the body with a blank line Limit the subject line to 50 characters Capitalize the subject line Do not end the subject line with a period Use the imperative mood in the subject line Wrap the body at 72 characters Use the body to explain what and why, not how Looks like pedantry until you understand the reasoning behind each one. Let me dig in, because honestly the why behind these rules is more useful than the rules themselves.\nThe subject line is special — Git treats it differently That first line isn\u0026rsquo;t just convention; Git itself treats it as the commit\u0026rsquo;s title. Tools like git log --oneline, git shortlog, git rebase, and pretty much every GitHub/GitLab UI use only that first line in summaries [1]. The blank line after it is how Git knows where the title ends and the body begins. Skip the blank line and your nicely written body gets mashed into the subject in half the tools you use.\nWhy 50 characters? It\u0026rsquo;s not arbitrary and it\u0026rsquo;s not a hard limit — think of it as a strong nudge. GitHub warns you at 50 characters and truncates at 72 with an ellipsis [1]. More importantly, the constraint forces clarity. If you can\u0026rsquo;t describe your change in 50 characters, that\u0026rsquo;s usually a hint that your commit is doing too many things (more on that later). The body wraps at 72 so that Git\u0026rsquo;s default indentation still fits inside an 80-column terminal without wrapping ugly [3].\nThe imperative mood thing — yes it actually matters This is the rule people roll their eyes at the most, so let me make the case. Write \u0026ldquo;Fix login redirect loop\u0026rdquo;, not \u0026ldquo;Fixed\u0026rdquo;, not \u0026ldquo;Fixes\u0026rdquo;, not \u0026ldquo;Fixing\u0026rdquo;.\nThe test that makes it click: your subject should complete the sentence \u0026ldquo;If applied, this commit will ___\u0026rdquo; [4].\n✅ If applied, this commit will Fix login redirect loop ❌ If applied, this commit will Fixed login redirect loop The second one reads like nonsense. There\u0026rsquo;s a deeper reason too — Git itself writes in the imperative. When you merge, Git generates \u0026ldquo;Merge branch \u0026lsquo;feature\u0026rsquo;\u0026rdquo;. When you revert, it writes \u0026ldquo;Revert \u0026hellip;\u0026rdquo;. Your commits should read as commands to the codebase, consistent with Git\u0026rsquo;s own voice [4]. Funny enough, despite all the preaching, only around 44% of commits on GitHub actually use the imperative mood [5]. So following this puts you in a minority that looks like it knows what it\u0026rsquo;s doing.\nA quick before/after:\n❌ Don\u0026rsquo;t ✅ Do fixed the bug. Fix null check in user serializer Updating README Document the env setup steps changes to api Add pagination to /orders endpoint WIP Add failing test for expired tokens What goes in the body The subject says what. The body is where you explain why this change, why now, and what you considered. Don\u0026rsquo;t narrate how — the diff already shows how. Explain the problem you were solving and the reasoning behind your approach [1].\nA real-ish example of a full message:\nCap retry attempts on payment webhook The webhook handler retried indefinitely on a 5xx from the billing provider. During their outage last Tuesday this hammered their API and triggered duplicate charge attempts for ~30 users. Limit retries to 3 with exponential backoff, and log the final failure so support can reconcile manually. Refunds for the affected accounts are tracked in TICKET-4821. Notice there\u0026rsquo;s not a single line about how the backoff is implemented. The code shows that. The message captures the stuff you\u0026rsquo;d otherwise lose forever — the outage, the duplicate charges, the ticket. That\u0026rsquo;s the irreplaceable part.\nOne practical tip: stop using git commit -m for anything non-trivial. The -m flag quietly trains you to write one-liners because writing a proper body with it is painful [6]. Just run git commit, let your editor open, and write like a human. Set your editor with git config --global core.editor \u0026quot;code --wait\u0026quot; (or vim, or whatever) and you\u0026rsquo;re set.\nWrite atomic commits — this is the real secret Here\u0026rsquo;s the thing almost nobody tells beginners: the quality of your commit message depends almost entirely on the quality of your commit. If your commit changes fifteen unrelated things, no message on earth can summarise it well. You\u0026rsquo;ll end up writing \u0026ldquo;various fixes\u0026rdquo; because that\u0026rsquo;s genuinely the only honest description.\nAn atomic commit is the smallest change that makes sense on its own and leaves the codebase in a working state [7]. One logical change. Don\u0026rsquo;t mix a bug fix with a formatting cleanup with a new feature.\nThere\u0026rsquo;s a dead-simple smell test for this: if your subject line needs the word \u0026ldquo;and\u0026rdquo;, your commit is probably two commits [8]. \u0026ldquo;Add search filter and fix navbar styling\u0026rdquo; — that\u0026rsquo;s two commits wearing a trench coat.\nWhy this is worth the discipline:\ngit bisect becomes a superpower. Small focused commits let bisect zero in on the exact change that introduced a bug [9]. Reverts are clean. You can undo one feature without accidentally reverting an unrelated fix that rode along with it [9]. Code review gets easier. Reviewers understand a small, single-purpose change far faster than a 600-line grab-bag. The history reads like a story. Each commit is one chapter in how the project got here [9]. The flow that works for me: stage selectively with git add -p (patch mode) so you commit only the related hunks, even if your working directory has a bunch of unrelated changes going on. And if you\u0026rsquo;ve already made a mess locally, that\u0026rsquo;s what interactive rebase is for — squash and split before you push.\nConventional Commits: structure for machines and humans Once you\u0026rsquo;ve nailed the basics, there\u0026rsquo;s a popular convention that adds a light structure on top: Conventional Commits. The format is dead simple [10]:\n\u0026lt;type\u0026gt;[optional scope]: \u0026lt;description\u0026gt; [optional body] [optional footer(s)] So your subject line starts with a type. The common ones:\nType When to use it feat A new feature fix A bug fix docs Documentation only style Formatting, whitespace — no logic change refactor Code change that neither fixes a bug nor adds a feature perf A performance improvement test Adding or fixing tests build / ci Build system or CI config chore Routine maintenance, deps, tooling Examples in the wild:\nfeat(auth): add passwordless email login fix(api): handle empty cart on checkout docs: clarify env setup in README refactor(parser): extract token validation Breaking changes get flagged two ways: a ! before the colon, or a BREAKING CHANGE: footer [10]:\nfeat!: drop support for Node 16 BREAKING CHANGE: minimum supported runtime is now Node 18. Why teams adopt it The payoff isn\u0026rsquo;t aesthetic — it\u0026rsquo;s automation. Because the type is machine-readable, tooling can:\nGenerate changelogs automatically from your commit history. Bump your semantic version correctly — fix → patch, feat → minor, BREAKING CHANGE → major [10]. Trigger releases and publish steps in CI without a human deciding the version. This isn\u0026rsquo;t a niche thing either. One review of 381 top NPM libraries found nearly 95% used Conventional Commits formatting, with over half hitting 80%+ compliance across their history [11]. If you maintain a library, this is close to table stakes now.\nHonestly, though — is it for everyone? On a solo project or a small internal app where you\u0026rsquo;re not auto-releasing anything, the feat:/fix: prefixes can feel like ceremony for ceremony\u0026rsquo;s sake. The seven rules from earlier matter far more than the prefix. Use Conventional Commits when the automation pays for the discipline. Don\u0026rsquo;t cargo-cult it because a popular repo does.\nThe anti-patterns — what makes a commit message bad Let me name names. These are the ones I see constantly, and why they hurt [12][8]:\n\u0026ldquo;Fix bug\u0026rdquo; — which bug? You will not remember. Name the bug. \u0026ldquo;Update\u0026rdquo;, \u0026ldquo;changes\u0026rdquo;, \u0026ldquo;stuff\u0026rdquo;, \u0026ldquo;wip\u0026rdquo; — zero information. The diff already told us something changed. Listing files: \u0026ldquo;Update user.rb and helper.js\u0026rdquo; — git show already lists the files. The message should explain the meaning of the change, not duplicate the file list [12]. \u0026ldquo;and\u0026rdquo; in the subject — almost always means the commit should be split [8]. Past tense / wrong mood — \u0026ldquo;Fixed\u0026rdquo;, \u0026ldquo;Added\u0026rdquo;, \u0026ldquo;Changing\u0026rdquo;. Pick imperative and stay consistent. A wall of one-liners — ten commits all saying \u0026ldquo;updates\u0026rdquo;. This is what git commit -m overuse produces [12]. The subject restating the obvious — \u0026ldquo;Make changes to the changes file\u0026rdquo;. Say what and why. Here\u0026rsquo;s a gut-check before you hit enter: read your subject line back as \u0026ldquo;If applied, this commit will ___\u0026rdquo; and ask whether someone who wasn\u0026rsquo;t there would understand it in six months. If the answer is no, spend the extra thirty seconds. It\u0026rsquo;s the cheapest investment in your future sanity you\u0026rsquo;ll ever make.\nTooling that keeps you honest Discipline is hard to sustain by willpower alone, so let the machines nag you instead:\nCommit message template — set a reminder of your own rules: git config --global commit.template ~/.gitmessage.txt Put a skeleton in that file (subject reminder, blank line, body reminder) and it shows up every time you commit. commitlint + Husky — a git hook that rejects commits that don\u0026rsquo;t follow Conventional Commits. Great for teams. Commitizen — an interactive prompt that walks you through building a properly formatted commit. Editor integration — most editors (and tools like GitKraken) highlight when your subject runs past 50 characters [13]. None of these write a good message for you — they just enforce the shape. The thinking is still your job. And these days an AI assistant can draft a decent message from your diff, which is genuinely useful, but treat it like a junior\u0026rsquo;s first draft. It can see what changed; it can\u0026rsquo;t see the outage last Tuesday or the ticket number. That context only lives in your head, and putting it in the message is the entire point.\nA workflow you can actually stick to Pulling it together, here\u0026rsquo;s what a sane commit habit looks like day to day:\nMake one logical change. If you drifted into unrelated edits, stage selectively with git add -p. Run git commit (no -m) so your editor opens. Write a subject under ~50 chars, imperative mood, capitalised, no period. Add a type prefix if your team uses Conventional Commits. Blank line, then a body explaining the why — but only if the change needs context. Trivial commits don\u0026rsquo;t need a body. Reference tickets or issues in the footer (Refs #421, Closes #88). Re-read it with the \u0026ldquo;If applied, this commit will ___\u0026rdquo; test before saving. That\u0026rsquo;s it. It adds maybe a minute per commit and saves hours later. For a deeper walkthrough with more examples, freeCodeCamp\u0026rsquo;s step-by-step guide is a solid companion read [14].\nThe best commit message isn\u0026rsquo;t the cleverest or the longest. It\u0026rsquo;s the one that answers the question your future teammate is going to be screaming at the screen: \u0026ldquo;Why on earth was this changed?\u0026rdquo; Answer that, and you\u0026rsquo;ve done the job.\nEnd\nSources How to Write a Git Commit Message - Chris Beams Mastering Git Commit Messages: Tim Pope\u0026rsquo;s 50/72 Formatting Guide The 50/72 Rule of Git – DevIQ Imperative Git commit messages in the active tense or mood - TheServerSide What % of Git commit messages use the imperative mood? - InitialCommit Best Practices for Git Commit Message - Baeldung on Ops Mastering Atomic Commits - LeanIX Engineering \u0026ldquo;and\u0026rdquo; as anti-pattern in git commit subject - Kosta Harlan How atomic Git commits dramatically increased my productivity - DEV Community Conventional Commits v1.0.0 Conventional Commits Specification - Wikipedia Git Commit Message Anti-Patterns - AMC How to Write a Good Git Commit Message - GitKraken How to Write Better Git Commit Messages – freeCodeCamp ","permalink":"https://cloudmato.com/posts/how-to-write-the-best-git-commit-message/","title":"How to Write the Best Git Commit Message"},{"content":"Two questions get mashed together constantly: \u0026ldquo;what is TLS termination\u0026rdquo; and \u0026ldquo;is SSL a transport layer thing?\u0026rdquo; People assume the answer to the second is obviously yes — it\u0026rsquo;s literally called Transport Layer Security, right? Well. That naming has fooled a lot of smart people, and the confusion bleeds straight into how folks reason about termination. So let me untangle both, because once the layer question clicks, termination stops feeling like magic.\nThe short answer to the layer question Here\u0026rsquo;s the blunt version: TLS does not run at the transport layer, even though its name says \u0026ldquo;Transport.\u0026rdquo; It runs on top of the transport layer.\nWhen your browser talks to a server over HTTPS, the actual transport is still TCP. That\u0026rsquo;s layer 4. TCP handles ports, sequencing, retransmission, the reliable byte-stream stuff. TLS sits above that, wraps the bytes in encryption, and hands the plaintext up to whatever application protocol you\u0026rsquo;re using (HTTP, SMTP, IMAP, gRPC, whatever). The official spec, RFC 8446, describes TLS 1.3 as a protocol that \u0026ldquo;allows client/server applications to communicate over the Internet in a way that is designed to prevent eavesdropping, tampering, and message forgery\u0026rdquo; — and it explicitly assumes a reliable transport like TCP underneath it [1].\nSo why the \u0026ldquo;Transport\u0026rdquo; in the name? Honestly, it\u0026rsquo;s mostly a historical accident of branding. The protocol began life as SSL (Secure Sockets Layer), built by Netscape in the mid-90s. When the IETF took it over and standardized it in 1999, they renamed it Transport Layer Security — partly to avoid the Netscape trademark baggage, partly because it secures data in transit [2]. The name describes intent, not its position in the OSI stack.\nWhere it actually fits in the OSI model If you try to slot TLS neatly into the seven-layer OSI model, you\u0026rsquo;ll struggle, and that\u0026rsquo;s not your fault. As the Wikipedia entry on TLS and plenty of network engineers put it, TLS just doesn\u0026rsquo;t fit cleanly [3]. The most common honest description:\nIt runs above the transport layer (layer 4 / TCP). It sits below the application layer (layer 7 / HTTP). In OSI terms, the functions it performs — encryption, session establishment — map roughly onto the presentation (6) and session (5) layers. In the TCP/IP model (the one that actually matches the real internet), there\u0026rsquo;s no separate presentation or session layer, so people often lazily file TLS under \u0026ldquo;transport\u0026rdquo; or \u0026ldquo;application\u0026rdquo; depending on their mood. Neither is precisely right. The most defensible thing you can say: TLS is a session/presentation-layer security protocol that depends on a transport-layer protocol to do its job [4].\nWhy does this matter for termination? Because TLS being a distinct layer that wraps TCP is exactly what lets a load balancer peel it off, deal with it, and pass the inner traffic onward. If encryption were baked into TCP itself, you couldn\u0026rsquo;t cleanly separate the two. The layering is the whole reason termination exists as a concept.\nSo what is TLS termination? TLS termination is the point where the encrypted connection ends and the traffic gets decrypted. That\u0026rsquo;s it. The word \u0026ldquo;terminate\u0026rdquo; just means \u0026ldquo;this is where the TLS tunnel stops.\u0026rdquo;\nIn a simple world, that point is your application server. Client opens an HTTPS connection straight to your app, the app holds the certificate and private key, it decrypts the request, processes it, encrypts the response. End to end, single hop.\nBut almost nobody runs it that way at scale. Instead, you put something in front — a load balancer, a reverse proxy like Nginx or HAProxy, an API gateway, a CDN edge node. That front device does the TLS handshake, decrypts the traffic, and forwards the now-plaintext request to your backend servers, usually over plain HTTP on a trusted internal network [5]. The \u0026ldquo;termination\u0026rdquo; has moved from your app to the edge. This is also called SSL offloading, because you\u0026rsquo;re offloading the cryptographic work off your backends.\nHere\u0026rsquo;s a concrete picture. A user hits https://yourshop.com. The request lands on your load balancer. The load balancer:\nCompletes the TLS handshake with the browser. Validates and presents the certificate. Decrypts the incoming bytes into a normal HTTP request. Forwards that request to one of your backend app servers (http://10.0.1.42:8080). Takes the backend\u0026rsquo;s plain HTTP response, re-encrypts it, sends it back to the browser. The browser thinks it had a secure conversation with yourshop.com — and it did, all the way to the load balancer. Your backend never touched a single byte of encryption.\nWhy bother terminating at the edge? Why introduce this extra step at all? A few genuinely good reasons:\nIt saves your backends from crypto work. TLS handshakes and bulk encryption cost CPU. Pushing all of it onto dedicated load balancers (often with hardware acceleration) frees your application servers to actually run application logic [6]. Certificate management gets sane. Instead of copying certs and private keys to fifty app servers and renewing all of them, you manage one cert in one place. Tools like AWS Certificate Manager will even auto-renew and rotate without downtime [7]. You unlock layer-7 features. Once the traffic is decrypted, the proxy can read it. That means path-based routing, header rewrites, sticky sessions, request-level metrics, response compression, caching, and Web Application Firewall (WAF) inspection. None of that is possible while the bytes are still encrypted [8]. Scaling is easier. Spin up a new backend instance and it just needs to speak plain HTTP. No TLS config, no cert provisioning per node. That last point about layer-7 features is the one people underrate. A load balancer cannot route based on the URL path — say, sending /api/* to one pool and /images/* to another — if it can\u0026rsquo;t see the URL. And it can\u0026rsquo;t see the URL while the request is encrypted. Visibility requires decryption. This single fact drives most architectural decisions around TLS.\nThe three models: termination, passthrough, re-encryption TLS termination isn\u0026rsquo;t a single thing — it\u0026rsquo;s one of three patterns, and picking the wrong one causes real pain. Here\u0026rsquo;s how they stack up.\nModel Where TLS decrypts Backend hop L7 features? End-to-end encrypted? Termination (offloading) At the proxy Plain HTTP Yes No Passthrough At the backend only Still encrypted No Yes Re-encryption (bridging) At the proxy, then again New TLS session Yes Yes Termination / offloading The default we just described. Decrypt at the edge, plain HTTP to the backend. Maximum features, simplest backends. The catch: traffic between your load balancer and your app servers travels unencrypted. On a locked-down private VPC that\u0026rsquo;s often acceptable. On a shared or untrusted network, it\u0026rsquo;s a liability — SSL offloading can leave that internal hop open to man-in-the-middle snooping if someone gets inside your network [8].\nPassthrough The proxy doesn\u0026rsquo;t decrypt anything. It forwards the encrypted stream byte-for-byte to the backend, which holds the certificate and terminates TLS itself. Clever proxies still route intelligently by peeking at the unencrypted SNI (Server Name Indication) field in the ClientHello — that tells them which hostname the client wants without decrypting the payload [9].\nThe upside is real security: encryption is unbroken from client all the way to the backend, decryption happens only at the destination. The downside is you lose everything at layer 7 — no path routing, no header manipulation, no WAF, no cookie-based sticky sessions. You\u0026rsquo;re basically a dumb TCP pipe. Passthrough makes sense when backends already manage their own certs, or when compliance demands the proxy never sees plaintext.\nRe-encryption / TLS bridging The \u0026ldquo;have your cake and eat it\u0026rdquo; option. The proxy terminates the client\u0026rsquo;s TLS, decrypts, does whatever layer-7 magic it needs, then opens a brand new TLS connection to the backend and re-encrypts before forwarding [10]. Red Hat\u0026rsquo;s OpenShift docs describe this \u0026ldquo;re-encrypt\u0026rdquo; route type exactly: TLS terminates at the router, then the router establishes fresh encryption to the pod [10].\nYou get inspection and an encrypted internal hop. The cost is double the crypto work and a bit more config (now you manage certs in two places). For regulated workloads — healthcare data under HIPAA, card data under PCI-DSS — this is frequently the right answer, because those frameworks expect encryption in transit across untrusted segments, not just at the front door [11].\nWhat actually happens when TLS \u0026ldquo;terminates\u0026rdquo; To terminate TLS you have to complete the handshake, so it\u0026rsquo;s worth knowing what that involves. People picture it as one magic step, but it\u0026rsquo;s a negotiated dance between client and server. Here\u0026rsquo;s the TLS 1.3 flow, roughly [1]:\nClientHello — the browser says hi, lists the cipher suites and TLS versions it supports, and (in 1.3) already throws in its key-share guess to save a round trip. ServerHello — the server picks a cipher suite, sends its key share, and starts encrypting almost immediately. Certificate + verification — the server proves its identity with its certificate; the client checks it against trusted Certificate Authorities. Finished — both sides confirm they\u0026rsquo;ve derived the same session keys, and from here everything is encrypted with fast symmetric crypto. TLS 1.3 is a big deal here because it slashed the handshake from two round trips down to one, and even supports 0-RTT resumption for returning clients [1]. If you\u0026rsquo;ve ever wondered why modern HTTPS sites feel snappier than they did years ago, the leaner handshake is a chunk of the reason. There\u0026rsquo;s a gorgeous byte-by-byte walkthrough at The Illustrated TLS 1.3 Connection if you want to see every field on the wire [12].\nOnce the handshake finishes, TLS doesn\u0026rsquo;t send raw bytes — it chops the conversation into records. Each record has a content type: handshake (22), application data (23), or alert (21) [3]. A neat privacy trick in TLS 1.3: the real content type gets hidden inside the encrypted payload, and the outer header always claims it\u0026rsquo;s \u0026ldquo;application data.\u0026rdquo; So a network observer can\u0026rsquo;t even tell handshake messages apart from your actual traffic [3]. The thing doing termination has to understand all of this record machinery — which is precisely why it\u0026rsquo;s non-trivial work you\u0026rsquo;d want to centralize.\nA word on SSL vs TLS, since the terms are a mess You\u0026rsquo;ll see \u0026ldquo;SSL termination\u0026rdquo; and \u0026ldquo;TLS termination\u0026rdquo; used interchangeably everywhere, including in AWS and Nginx docs. Technically that\u0026rsquo;s wrong, and it\u0026rsquo;s the same naming confusion from the top of this article.\nSSL is dead. All versions of SSL are deprecated and insecure. The IETF officially killed SSL 3.0 in June 2015, largely thanks to the POODLE attack discovered by Google researchers in 2014 — an exploit that let an attacker downgrade a connection to SSL 3.0 and decrypt session cookies one byte at a time [13]. TLS replaced it with stronger ciphers, better authentication, and fixes for exactly those padding and renegotiation holes [13].\nSo why does everyone still say \u0026ldquo;SSL\u0026rdquo;? Pure legacy. The term got burned into the industry\u0026rsquo;s brain during the 90s and early 2000s, certificate vendors still sell \u0026ldquo;SSL certificates,\u0026rdquo; and people search for \u0026ldquo;SSL\u0026rdquo; far more than \u0026ldquo;TLS\u0026rdquo; [14]. When someone says \u0026ldquo;SSL termination\u0026rdquo; in 2026, they almost certainly mean TLS termination — the protocol underneath is TLS 1.2 or 1.3. Don\u0026rsquo;t let the vocabulary trip you up, but do make sure your actual config disables SSL and old TLS versions. The history of these versions, if you\u0026rsquo;re curious, is laid out nicely by The SSL Store [15].\nThe security trade-off you can\u0026rsquo;t ignore Here\u0026rsquo;s where I\u0026rsquo;ll get opinionated. Plain TLS termination — decrypt at the edge, plaintext to the backend — is fine until it isn\u0026rsquo;t. The risk is the internal hop. Anyone with a foothold inside your network, a misconfigured security group, or a compromised neighbouring service can potentially read traffic that you assumed was protected because the user saw a padlock.\nFor a lot of internal-only, well-segmented setups, that risk is acceptable and the simplicity wins. But the moment you\u0026rsquo;re handling regulated data, rethink it:\nHIPAA expects electronic protected health information to stay encrypted across untrusted networks, not just on the public hop [11]. PCI-DSS and similar frameworks push for strong encryption end to end for cardholder data [11]. Zero-trust architectures assume the internal network is hostile by default, which rules out plaintext backend hops entirely. That\u0026rsquo;s where mutual TLS (mTLS) enters. Regular TLS authenticates only the server to the client. mTLS adds a second check: the client also presents a certificate, so both sides cryptographically prove who they are [16]. In a microservices mesh, mTLS between services means a rogue pod can\u0026rsquo;t just start talking to your payment service — it doesn\u0026rsquo;t have a valid cert. Your load balancer can terminate inbound mTLS from clients and separately establish mTLS to the backends [11]. Google Cloud, Istio, and most service meshes bake this in now [16].\nThe practical rule I\u0026rsquo;d give: decide where your trust boundary is, then put your termination point at that boundary. If the edge is your boundary, terminate there. If you don\u0026rsquo;t trust your own network, re-encrypt or use mTLS so the protected zone extends all the way to the app.\nPutting it together If you only remember a few things:\nSSL/TLS isn\u0026rsquo;t a transport-layer protocol despite the name. It rides on top of TCP (the real transport) and behaves like a session/presentation layer wrapper. That layering is what makes termination possible at all. TLS termination is just the spot where encryption ends and decryption happens — usually pushed to a load balancer or proxy to save backend CPU, centralize certs, and unlock layer-7 routing and inspection. There are three flavors: terminate (plaintext backend), passthrough (encrypted backend, no L7), and re-encrypt (encrypted backend with L7). Pick based on whether you trust your internal network. \u0026ldquo;SSL\u0026rdquo; is a dead protocol but a live word. When people say SSL termination they mean TLS. The next time someone confidently tells you SSL is \u0026ldquo;a transport layer thing,\u0026rdquo; you can gently point out that the name is marketing, not architecture — and that understanding the difference is exactly what lets you reason clearly about where to terminate.\nSources RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3 SSL Deprecation: Why TLS took over internet security — Sectigo Transport Layer Security — Wikipedia Transport Layer Security — which layer of OSI model? — Medium What is SSL/TLS termination? — HAProxy New – TLS Termination for Network Load Balancers — AWS Understanding TLS Termination with Load Balancers in AWS — Cloudericks TLS Termination Models: Passthrough vs Termination vs Bridging — DEV Community Understanding Nginx: TLS Termination vs. TLS Passthrough — Medium 3 ways to encrypt communications with Red Hat OpenShift Is TLS Enough for HIPAA? — HIPAA Vault The Illustrated TLS 1.3 Connection: Every Byte Explained SSL vs TLS: What\u0026rsquo;s the Difference — Authgear Is SSL Deprecated? Transition from SSL to TLS — SSL Dragon SSL and TLS Versions: Celebrating 30 Years of History — The SSL Store Mutual TLS overview — Google Cloud Load Balancing ","permalink":"https://cloudmato.com/posts/tls-termination-explained/","title":"TLS Termination Explained (and Is SSL Really Transport Layer?)"},{"content":"Most developers I talk to have this vague understanding about testing — they know they should do it, but they\u0026rsquo;re not always clear on which tests do what and why it matters. So you write a unit test here, run some manual clicks there, and hope the whole thing works. That\u0026rsquo;s not a strategy. Testing has distinct types for distinct purposes.\nLet me walk you through what each testing type actually does and, more importantly, which ones companies genuinely implement versus which ones are nice-to-have theory.\nThe Testing Pyramid Before diving into individual types, understand the mental model. Most companies follow a testing pyramid: lots of unit tests at the base (fast, cheap), fewer integration tests in the middle (slower, more complex), and a small number of end-to-end tests at the top (slowest, most fragile). [1]\nUnit Testing Unit tests examine individual functions or methods in isolation. A developer writes a test that checks whether a specific piece of code produces the expected output for given inputs. [2]\nFor example, if you have a function that calculates tax on a price, a unit test verifies that calculateTax(100, 0.1) returns exactly 10. Nothing else matters — the database isn\u0026rsquo;t involved, the API isn\u0026rsquo;t called, the UI doesn\u0026rsquo;t render.\nWhy it matters: Unit tests catch bugs early. Developers write them as they code, so broken logic surfaces immediately instead of hours later when it reaches QA.\nTool examples: Jest (JavaScript), pytest (Python), JUnit (Java), NUnit (C#).\nIntegration Testing Integration tests verify that different units work together. Unlike unit tests, integration tests let components talk to each other — your code can now hit the database, call an API, or interact with the cache. [3]\nExample: A unit test verifies that your data transformation function works. An integration test verifies that your function correctly reads data from the database and transforms it.\nWhy it matters: Components work fine individually but fail together. Integration tests catch those boundary issues.\nCommon integration scenarios:\nCode + database queries Code + external APIs Microservices talking to each other Frontend talking to backend Smoke Testing Smoke testing is quick, shallow, and critical. It answers one question: does the build work at all? [2]\nRun the app. Click the main flows. Can you log in? Can you navigate the dashboard? Does the checkout button exist? If any of these fail, the build is broken and you stop — there\u0026rsquo;s no point running detailed tests on a foundation that\u0026rsquo;s cracked.\nThink of smoke testing as a \u0026ldquo;build stability gate.\u0026rdquo; It happens before any serious QA work begins. If it fails, developers get it back immediately.\nWhy it matters: It saves time. Regression and acceptance testing are expensive. Don\u0026rsquo;t run them if the basic app is dead.\nTypical smoke test examples:\nLogin and logout Load the main page Navigate to key features Basic CRUD operations (Create, Read, Update, Delete) Regression Testing Regression testing repeats existing tests after code changes to ensure you haven\u0026rsquo;t broken anything that was already working. [2] [4]\nYou fix a bug in the payment module. Now you run all the old tests that cover payment, checkout, order history, notifications — everything that touches or depends on that module. If something breaks, the regression caught it before production.\nWhy it matters: As a codebase grows, the surface area of potential damage increases. Regression testing is how you confidently change old code without fear.\nKey insight: Regression testing is usually automated because you\u0026rsquo;re running the same tests over and over. That\u0026rsquo;s where test automation tools shine.\nAcceptance Testing Acceptance testing answers: Does this software do what the business asked for? [5]\nUnlike technical testing, acceptance testing is owned by business stakeholders — product managers, business analysts, sometimes customers themselves. They define acceptance criteria during requirements gathering, and testers verify the software meets those criteria.\nExample: The requirement is \u0026ldquo;users can filter products by price and category.\u0026rdquo; Acceptance testing confirms that feature works as expected from a user\u0026rsquo;s perspective, not from a technical architecture perspective.\nKey difference: Unit, integration, and smoke tests are run by developers or QA. Acceptance tests are run by business users or QA working with business users.\nEnd-to-End Testing (E2E) End-to-end testing simulates real user journeys through the entire application. [5]\nA user opens the app → browses products → adds items to cart → checks out → receives confirmation → gets an email. E2E tests verify this entire flow works, including all systems involved (frontend, backend, database, payment gateway, email service).\nWhy it matters: Unit tests might pass, integration tests might pass, but if the user journey breaks, nothing else matters.\nReal example: An e-commerce site. Unit test checks the price calculator. Integration test checks that prices load from the database. E2E test actually buys a product end-to-end and confirms the order appears in the user\u0026rsquo;s account and an email is sent.\nPerformance Testing Performance testing measures speed, stability, and resource usage under load. [5]\nSpecific aspects:\nResponse time: How long does a request take? Throughput: How many requests can the system handle per second? Scalability: Does it handle 10 users? 1000? 10,000? Resource usage: CPU, memory, database connections under load. Performance testing isn\u0026rsquo;t just \u0026ldquo;is it fast?\u0026rdquo; — it\u0026rsquo;s \u0026ldquo;does it stay stable and fast under realistic load?\u0026rdquo;\nSystem Testing \u0026amp; Other Types Beyond these, you\u0026rsquo;ll encounter:\nTest Type What it checks Who runs it System Testing Complete integrated system against requirements QA Team Security Testing Vulnerabilities, data exposure, authentication flaws Security team or specialized QA Usability Testing Is the UI intuitive? Do users understand how to use it? QA or real users API Testing Does the API contract work? Are responses correct? Developers or QA Database Testing Data integrity, query performance, backup/recovery QA or DBAs Which Tests Do Companies Actually Implement? Theory is one thing. Practice is another. Here\u0026rsquo;s what surveys of real companies show: [6] [7] [8]\nMost companies use:\nUnit testing (widely adopted, especially in agile/DevOps shops) Regression testing (53% of companies automated this) Integration testing (45% of companies use automated integration testing) Smoke testing (quick win, high ROI) API testing (56% of companies, crucial for microservices) Performance testing (40% of companies use it) Less common:\nAcceptance testing (often happens but not always formally structured) End-to-end testing (expensive, slow, fragile, so kept minimal) Security testing (depends on industry; critical for fintech/healthcare, ignored by startups) The automation split: Companies aim for roughly 50:50 manual to automated testing, though many are moving toward 75% automation. [6] About 77% of companies have some automated testing in place. [6]\nThe Real Picture: What\u0026rsquo;s Actually In Your Test Suite? If you\u0026rsquo;re at a typical mid-size tech company, your testing probably looks like this:\nDevelopers write unit tests as they code (maybe 70% coverage, maybe 30% — depends on discipline) CI/CD pipeline runs unit + integration tests automatically on every commit Smoke test runs to gate the build QA runs regression tests (many automated, some manual) before release End-to-end tests exist but are kept minimal because they\u0026rsquo;re slow and brittle Manual testing happens for exploratory testing, edge cases, and things automation can\u0026rsquo;t easily check (like \u0026ldquo;does it look right?\u0026rdquo;) Startups often skip steps 5 and 6. Enterprise shops formalize acceptance testing. Security-critical industries (fintech, healthcare) add security and compliance testing.\nThe bottom line: No company tests everything. You prioritize based on risk, speed, and cost. Unit tests are cheap and fast — you write lots of them. E2E tests are expensive and slow — you write just enough to cover critical paths.\nSources Types of Software Testing - GeeksforGeeks Functional Testing Types: Unit, Sanity, Smoke, and More | Perforce BlazeMeter The different types of testing in software | Atlassian Smoke Testing vs. Regression Testing: Key Differences - Ranorex Unit Testing vs End To End Testing – Key Differences 32 Software Testing Statistics for Your Presentation in 2025 Latest Software Testing Statistics (2026 Edition) Software Test Automation Statistics and Trends for 2025 | DogQ ","permalink":"https://cloudmato.com/posts/types-of-software-testing-guide/","title":"All Types of Software Testing Explained"},{"content":"Everyone talks about SOLID principles. Your senior dev mentions them in code review. Your architecture docs reference them. But why do actual projects become unmaintainable garbage despite teams \u0026ldquo;knowing\u0026rdquo; SOLID? The answer: understanding SOLID and actually building with it are two completely different things [1].\nMost developers learn about SOLID early, nod along, then immediately violate these principles the moment a deadline hits. Let me show you where the rubber meets the road — and why it\u0026rsquo;s harder than it sounds.\nWhat is SOLID, and Why Should You Care? SOLID is five design principles that reduce tight coupling and improve code maintainability [1]. The acronym stands for:\nSingle Responsibility Principle — A class should have one reason to change Open/Closed Principle — Classes should be open for extension, closed for modification Liskov Substitution Principle — Subtypes must be substitutable for their base types Interface Segregation Principle — Clients shouldn\u0026rsquo;t depend on interfaces they don\u0026rsquo;t use Dependency Inversion Principle — Depend on abstractions, not concrete implementations Without these principles, codebases become rigid. One change in one place breaks five other places. New features require touching legacy code that \u0026ldquo;works but nobody wants to touch it.\u0026rdquo; Tests are impossible to write. That\u0026rsquo;s where most projects live [2].\nWith SOLID, your codebase breathes. Changes stay isolated. New features don\u0026rsquo;t ripple through the system. Tests run in isolation. Onboarding new developers doesn\u0026rsquo;t require archaeological digs through 10-year-old code.\nReal-World Violations: Where SOLID Goes Missing The God Class — Everything in One Place I\u0026rsquo;ve seen this a hundred times. A UserManager class that handles user data, email sending, password hashing, logging, payment processing, and authentication. All in one file. All tangled together [3].\nclass UserManager { public void createUser(String email, String password) { validateEmail(email); hashPassword(password); saveToDatabase(email, password); sendWelcomeEmail(email); logUserCreation(email); chargeSignupFee(email); trackAnalyticsEvent(email); } private void sendWelcomeEmail(String email) { /* ... */ } private void saveToDatabase(String email, String password) { /* ... */ } private void chargeSignupFee(String email) { /* ... */ } // 50 more methods doing unrelated things } This is Single Responsibility Principle violated completely. Want to change how you send emails? You modify UserManager. Want to change payment logic? You modify UserManager. Want to add a new log format? You modify UserManager. Now you\u0026rsquo;re testing payment code when you just wanted to tweak the welcome email template.\nThe fix: Split into separate classes [1]. One handles user persistence. Another sends emails. A third processes payments. A fourth handles logging. Now each class has one reason to change.\nTight Coupling — The Payment System Problem You write code that directly depends on a specific payment provider: StripePaymentProcessor. It works. Your business grows. Now you want to support PayPal. Or cryptocurrency. Or some new local payment system [4].\nclass OrderService { private StripePaymentProcessor stripe = new StripePaymentProcessor(); public void processOrder(Order order) { // ... validation code ... stripe.charge(order.getAmount()); } } To add PayPal support, you modify OrderService. To add crypto, you modify it again. You\u0026rsquo;re modifying business logic code for every payment provider you add. That violates the Open/Closed Principle — your code should be open for extension (new payment types) but closed for modification [2].\nThe fix: Depend on an abstraction:\ninterface PaymentGateway { void charge(BigDecimal amount); } class OrderService { private PaymentGateway gateway; public OrderService(PaymentGateway gateway) { this.gateway = gateway; } public void processOrder(Order order) { gateway.charge(order.getAmount()); } } Now add PayPal? Create a PayPalPaymentGateway, inject it, done. Your OrderService never changes [4].\nFat Interfaces — Forcing Implementations of Unused Methods You create an interface that\u0026rsquo;s \u0026ldquo;comprehensive\u0026rdquo;:\ninterface PaymentProcessor { void charge(BigDecimal amount); void refund(BigDecimal amount); void setupRecurringPayment(BigDecimal amount, Frequency frequency); void validateCard(String cardNumber); void handleChargebackDispute(String transactionId); } Then a CryptoCurrencyPayment class implements this interface. Except crypto doesn\u0026rsquo;t do recurring payments. It doesn\u0026rsquo;t do chargebacks. The developers are forced to throw NotImplementedException for half the methods [3].\nThis violates the Interface Segregation Principle. Create smaller, focused interfaces instead:\ninterface Chargeable { void charge(BigDecimal amount); } interface Refundable { void refund(BigDecimal amount); } interface RecurringBillable { void setupRecurring(BigDecimal amount); } Now CryptoCurrencyPayment implements only Chargeable. No fake implementations. No confusion [3].\nWhy It\u0026rsquo;s So Hard to Follow SOLID Principles Understanding why we violate SOLID is more important than memorizing the rules.\nThe Deadline Trap You\u0026rsquo;re on sprint day 9 of a 10-day sprint. The feature needs to be done. You could refactor the UserManager class, split it properly, inject dependencies. Or you could add a method to the existing class in 5 minutes.\nYou add the method [5].\nTomorrow, another deadline. Another small addition to the existing class. By week two, you\u0026rsquo;ve violated SOLID so many times that fixing it would take days. By month two, nobody dares touch that code. This is how most projects end up. Not because developers are lazy. Because deadlines are real and refactoring costs time upfront.\nThe Vagueness of the Principles Single Responsibility sounds simple: one reason to change. But what\u0026rsquo;s \u0026ldquo;one reason\u0026rdquo;? Is a User class with email and password validation one responsibility or two? Is a Payment class handling both charge and refund one responsibility? [5]\nDifferent developers interpret this differently. I\u0026rsquo;ve seen teams split a user email into five separate classes (over-engineering). I\u0026rsquo;ve seen other teams put 50 methods in one class (under-engineering). There\u0026rsquo;s no bright line.\nOver-Engineering and Over-Abstraction Junior developers read about SOLID and go wild. They apply every principle to every problem. A simple form validation class gets refactored into three abstract classes, two interfaces, and a dependency injection container [5].\nThe code becomes harder to read, not easier. You need to jump between five files to understand what should be three lines of logic. Your test suite becomes more complex than the actual code.\nSOLID principles are tools, not laws. A simple script doesn\u0026rsquo;t need SOLID. A utility function doesn\u0026rsquo;t need SOLID. A one-off data processing job doesn\u0026rsquo;t need SOLID. SOLID matters for codebases that will live for years and need continuous change [5].\nTeam Alignment is Hard Even if your team agrees to follow SOLID, interpreting it is a source of constant debate [5].\nSenior dev says: \u0026ldquo;This class has too many methods, split it.\u0026rdquo; Junior dev says: \u0026ldquo;But it\u0026rsquo;s all related to payments, should be together.\u0026rdquo; Architect says: \u0026ldquo;Neither of you are thinking about the interface segregation issue here.\u0026rdquo; Six months in, your codebase looks like three different people wrote it with three different interpretations of SOLID. Code review becomes about debating principles instead of shipping features.\nLegacy Code is Expensive to Refactor Most projects don\u0026rsquo;t start clean. They start with one file doing everything. Then it grows. Then SOLID matters. Then refactoring it becomes a nightmare.\nYou can\u0026rsquo;t just split the UserManager class. It has 500 dependencies throughout the codebase. Database connection strings hardcoded. Configuration baked in. Global state scattered around [5].\nRefactoring it \u0026ldquo;properly\u0026rdquo; means touching 200 files. The risk of breaking something is high. Your tests (if they exist) are slow and fragile because they depend on the God class.\nWhy Projects Still Need SOLID Despite all these challenges, projects that follow SOLID are dramatically easier to maintain [1]. Here\u0026rsquo;s the thing: you don\u0026rsquo;t feel the pain of violating SOLID for the first few months. You feel it after a year.\nAfter a year of adding features to the God class, that one payment processing method is now 200 lines long. It handles edge cases for three different payment providers, legacy payment types, and a VIP user tier system. When you need to add a fourth provider, you\u0026rsquo;re terrified to change this method.\nAfter a year of tight coupling, refactoring a database layer means touching 50 files. After a year of fat interfaces, adding a new implementation requires writing stub methods in five classes.\nSOLID is expensive upfront. It\u0026rsquo;s cheap long-term.\nProjects that never learned this lesson end up with \u0026ldquo;legacy code\u0026rdquo; that can\u0026rsquo;t be changed because it\u0026rsquo;s too risky. New features take weeks instead of days. Simple bug fixes require understanding six intertwined classes. That\u0026rsquo;s when people rewrite the entire system (which is almost never the solution).\nEnd\nSources SOLID Principles with Real Life Examples - GeeksforGeeks SOLID Design Principles Explained: Building Better Software Architecture - DigitalOcean SOLID Design Principles: The Single Responsibility Explained - Stackify The Dependency Inversion Principle in Java - Baeldung 5 Problems Faced When Using SOLID Design Principles - Better Programming ","permalink":"https://cloudmato.com/posts/solid-principles-real-world-examples/","title":"Why SOLID Principles Matter \u0026 Why Developers Skip Them"},{"content":"Most developers lump all indexes together — \u0026ldquo;just something that makes queries fast\u0026rdquo; — without understanding that Elasticsearch and Oracle DB are solving completely different problems. They index the same data in fundamentally opposite ways, and that difference shapes everything about how they perform.\nLet me show you why a full-text search on Oracle feels like pulling teeth, while Elasticsearch makes it look trivial.\nThe Core Problem: Two Different Use Cases Oracle databases are built to answer questions like: \u0026ldquo;Give me the row where user_id = 5\u0026rdquo; or \u0026ldquo;Find all orders between January 1 and January 31.\u0026rdquo; Exact matches and range queries. The data is structured, indexed by column, and queries are usually precise.\nElasticsearch was built for: \u0026ldquo;Find me all documents containing \u0026lsquo;microservices\u0026rsquo; or \u0026lsquo;distributed systems\u0026rsquo;, and rank them by relevance.\u0026rdquo; Full-text search at scale. The data can be messy, queries are fuzzy, and users expect results ordered by relevance, not just returned.\nThese are not just different use cases — they\u0026rsquo;re opposite use cases. Oracle designed for one breaks when you ask for the other.\nOracle\u0026rsquo;s B-Tree: Built for Precision Oracle uses B-tree (balanced tree) indexes. Here\u0026rsquo;s how they work [1]:\nA B-tree is an ordered, sorted structure. Imagine a balanced tree turned upside down. At the top are branch blocks (interior nodes) that guide your search. At the bottom are leaf blocks that store the actual key values and ROWIDs (the physical location of rows in the table).\nWhen you search for a value, the database walks down the tree: branch block → branch block → leaf block → found. The depth of the tree is shallow (usually 2-4 levels even for millions of rows), so lookups are blazingly fast — about as many disk reads as the tree height.\nEach leaf block points to the next and previous leaf blocks in sorted order [1]. This lets Oracle do range scans efficiently. Need all users with IDs from 100 to 200? Once you find the first one, hop through the linked leaf blocks. No jumping around.\nThe price? B-trees are built for one column at a time (unless you use composite indexes). They\u0026rsquo;re optimized for lookups, not for \u0026ldquo;find anything containing this word.\u0026rdquo;\nElasticsearch\u0026rsquo;s Inverted Index: Built for Search Elasticsearch uses something completely different: an inverted index [2]. And here\u0026rsquo;s the key insight — it\u0026rsquo;s inverted compared to how we normally think about data.\nNormal way: Document → list of words in that document.\nInverted way: Word → list of documents containing that word.\nHere\u0026rsquo;s a concrete example. Say you have three documents:\nDoc 1: \u0026ldquo;Elasticsearch is fast\u0026rdquo; Doc 2: \u0026ldquo;Oracle is a database\u0026rdquo; Doc 3: \u0026ldquo;Elasticsearch and Oracle are databases\u0026rdquo; An inverted index stores it like this:\nTerm Documents elasticsearch [1, 3] fast [1] oracle [2, 3] database [2, 3] is [1, 2, 3] To find all documents containing \u0026ldquo;elasticsearch\u0026rdquo; or \u0026ldquo;oracle\u0026rdquo;, the index just looks up both terms and merges their document lists. No table scan. No checking every document. Instant.\nOracle can\u0026rsquo;t do this efficiently because it indexed documents by document ID, not by the words inside them. Full-text search becomes a full table scan [3].\nHow Elasticsearch Actually Indexes Data Indexing in Elasticsearch is more complex than a simple inverted index [4]:\nText is tokenized — \u0026ldquo;Elasticsearch is fast\u0026rdquo; becomes [\u0026ldquo;elasticsearch\u0026rdquo;, \u0026ldquo;is\u0026rdquo;, \u0026ldquo;fast\u0026rdquo;] Tokens are normalized — lowercasing, stemming (run → runn, runner → runn), removing stop words (is, a, the) Tokens are stored in the inverted index — mapping each unique token to document IDs The tokenization and filtering happen during indexing, not during search. This is why searching is so fast — the heavy lifting is done upfront.\nEach index in Elasticsearch is divided into shards [5]. A shard is a self-contained Lucene index. If your index gets huge, Elasticsearch splits it across multiple shards, and those shards live on different nodes (computers) in a cluster. This is horizontal scaling — more documents? Add more shards. Add more shards? Add more nodes.\nReplicas are copies of shards. Primary shard on node A, replica on node B [5]. If node A dies, node B takes over. More replicas also mean more read capacity — queries can hit any replica.\nOracle doesn\u0026rsquo;t work this way. You can shard data manually (partition tables), but it\u0026rsquo;s not native to how the index works.\nHead-to-Head: Oracle vs Elasticsearch Aspect Oracle B-Tree Elasticsearch Inverted Index Best for Exact matches, range queries (WHERE id = 5, WHERE date BETWEEN X AND Y) Full-text search, relevance ranking, fuzzy queries Search type Point lookup Term lookup Performance on \u0026ldquo;find all docs with word X\u0026rdquo; Full table scan (painful at scale) Hash lookup into inverted index (nanoseconds) Data structure Ordered tree, one column per index Flat inverted map, term → documents Scaling Vertical (bigger server) or manual sharding Horizontal (more nodes, more shards) Rank results by relevance No. Returns matches, not ranked. Yes. Scores by TF-IDF and BM25 by default. Update cost Rebalance tree nodes (O(log N)) Rewrite affected posting lists Suitable for structured data Yes, optimal for it. Not great. Better for semi-structured / text. Why This Matters I\u0026rsquo;ve watched teams try to bolt full-text search onto Oracle using triggers and custom tables. It works, barely. Or they use Oracle\u0026rsquo;s full-text indexing (CTXSYS), which is slow and expensive [1].\nThen they move the same workload to Elasticsearch and wonder why it\u0026rsquo;s 100x faster. It\u0026rsquo;s not magic. It\u0026rsquo;s because Elasticsearch\u0026rsquo;s data structure is designed for the problem.\nConversely, if you\u0026rsquo;re doing ACID transactions and complex joins on normalized data, Elasticsearch is the wrong tool. It doesn\u0026rsquo;t guarantee consistency in the way Oracle does. It\u0026rsquo;s eventual-consistent. No transactions. No foreign keys.\nWhen to Use Each Use Oracle (or Postgres, MySQL, SQL Server) when:\nData is structured and relational You need ACID guarantees Queries are precise (exact matches, ranges) You have \u0026lt; 1TB of hot data Use Elasticsearch when:\nYou\u0026rsquo;re indexing text or logs Users search with keywords, not exact values You need relevance ranking You need to scale horizontally to billions of documents A lot of projects use both. Oracle for transactional data, Elasticsearch as a search layer on top.\nThe confusion comes from calling them both \u0026ldquo;indexes.\u0026rdquo; They\u0026rsquo;re not the same thing. One is a tree that orders values. The other is an inverted map that groups documents by keywords. Different problems, different solutions.\nEnd\nSources How Oracle B-tree Indexes Work Inverted Index \u0026amp; Elasticsearch: How Modern Search Works at Scale What is an Elasticsearch index? | Elastic Blog Index fundamentals | Elastic Docs Clusters, nodes, and shards | Elastic Docs Oracle Indexes and Index-Organized Tables What is Elasticsearch? How It Works \u0026amp; Complete Guide ","permalink":"https://cloudmato.com/posts/elasticsearch-vs-oracle-indexing/","title":"How Elasticsearch Differs From Oracle Indexing"},{"content":"Everyone conflates these three concepts. You see them mentioned together constantly, people use the words interchangeably, and most articles mix them up or bury the differences in jargon. Here\u0026rsquo;s what they actually are, why they\u0026rsquo;re different, and when to use them. [1]\nDebouncing: Wait Until The Storm Passes Debouncing delays execution until after a period of inactivity.\nImagine someone typing into a search box. Every keystroke is an event. Without debouncing, you\u0026rsquo;d fire an API request with every single keystroke — character 1, character 2, character 3, and so on. That\u0026rsquo;s wasteful.\nWith debouncing, you set a timer. When the user types a character, the timer starts. If another character comes in before the timer expires, you restart the timer. This repeats until the user stops typing for the full duration. Only then does the function execute. [2]\nExample: User types \u0026ldquo;react\u0026rdquo;. Keystrokes happen at times 0ms, 50ms, 100ms, 150ms. If your debounce delay is 300ms, the function runs at 450ms (300ms after the last keystroke at 150ms).\nUse debouncing when you care about the final result, not every intermediate state. Common cases:\nSearch field autocomplete (wait until user stops typing) Form validation (check validity after user stops editing) Window resize handlers (update layout once resize is done) API calls triggered by user input Throttling: Steady The Stream Throttling limits execution to a fixed interval, no matter how often the event fires.\nImagine scrolling down a webpage. The scroll event fires continuously — dozens of times per second. Without throttling, you\u0026rsquo;d update the page layout or track analytics dozens of times per second, which is overkill.\nWith throttling, you say \u0026ldquo;execute this function at most once every 300 milliseconds.\u0026rdquo; If the event fires 50 times in 1 second, only 3-4 executions actually happen, spaced 300ms apart. The intermediate events are ignored. [3]\nExample: Scroll event fires 50 times in 1 second. Throttle delay is 300ms. Function executes at 0ms, 300ms, 600ms, 900ms — roughly 3-4 times total.\nUse throttling when you need ongoing updates while the user is actively interacting. Common cases:\nScroll tracking (analytics or infinite scroll) Window resize (reflow layout responsively) Mouse movement (drag handlers, animations) Real-time API polling The Key Difference Debouncing is coalesce-based — burst of events → single execution at the end.\nThrottling is rate-based — steady stream of executions at fixed intervals.\nNot the same thing. Not even close. [4]\nRate Limiting: The Server\u0026rsquo;s Bouncer Rate limiting sets a hard maximum: X requests per time window.\nThis is different from debounce and throttle. Those are about controlling your own code\u0026rsquo;s execution frequency. Rate limiting is about controlling who can access your system.\nRate limiting says: \u0026ldquo;You can make 100 requests per minute. That\u0026rsquo;s it.\u0026rdquo; The 101st request gets rejected, usually with an HTTP 429 error. It\u0026rsquo;s a policy enforced by the server (or API gateway) to protect resources and prevent abuse. [5]\nRate Limiting vs Throttling: The Confusion Here\u0026rsquo;s where people get confused. Some use \u0026ldquo;throttling\u0026rdquo; to mean rate limiting. Some sources use the terms interchangeably. But they operate at different layers:\nThrottling (in performance context): Client-side technique to reduce function call frequency Rate limiting (in API context): Server-side rule that rejects or queues excess requests API throttling: Server-side technique that slows down request processing instead of rejecting them Concept Layer Action Effect Debounce Client Delay execution Waits for silence Throttle Client/Server Limit frequency Steady rate Rate Limit Server Reject or queue Hard ceiling Can You Use Them Together? Yes. Actually, you should, in the right scenarios. [6]\nDebounce + Throttle: This is rare but valid. Imagine a text editor that saves drafts. Debounce captures that the user stopped typing, then throttle ensures you don\u0026rsquo;t hammer the server even if the user types continuously. Debounce waits for silence, throttle provides a safety net during activity.\nDebounce/Throttle + Rate Limiting: This is where it matters most. Frontend throttling and debouncing optimize your own requests, but they don\u0026rsquo;t protect you if a user hammers your API directly (e.g., via curl, or a bug sends requests). Rate limiting on the backend ensures no single user or client can overwhelm your system regardless of their frontend code. Think of it as defense in depth.\nExample: A search field uses debouncing (wait until user stops typing) + backend rate limiting (max 10 searches per minute per user). The debounce prevents noise. The rate limit prevents abuse.\nCommon pattern: Debounce on frontend for UX, throttle for heavy computation or animations, rate limit on backend for protection. Each solves a different problem.\nReal-World Example Building a user search feature:\nDebounce the input — wait 300ms after the user stops typing before making the API call. Reduces noise from every keystroke. Throttle the API response handler — if responses arrive quickly, process them at most once per 200ms. Avoids UI thrashing. Rate limit on backend — allow max 30 search requests per user per minute. Prevents someone spamming your database. All three working together, each addressing a different concern.\nNot Interchangeable The worst mistake is treating these as options for the same problem. They\u0026rsquo;re not. If you need to wait for the user to stop typing, debounce is the answer — throttling won\u0026rsquo;t do it. If you need to protect your API from abuse, rate limiting is the answer — debouncing won\u0026rsquo;t do it.\nPick the right tool. They solve different problems, even though they all involve timing.\nEnd\nSources Difference between Debouncing and Throttling - GeeksforGeeks Debounce - Glossary - MDN Web Docs Throttling vs. Debouncing Explained | Built In Debounce and Throttling: What They Are and When to Use Them | Medium API Throttling vs. API Rate Limiting - GeeksforGeeks Understanding the Differences Between Rate Limiting, Debouncing, and Throttling - Inngest Blog ","permalink":"https://cloudmato.com/posts/debounce-throttle-rate-limiting/","title":"Debounce vs Throttle vs Rate Limiting"},{"content":"If you\u0026rsquo;ve heard \u0026ldquo;neural network\u0026rdquo; thrown around in tech circles, you probably imagined something biological. The term neuron can make beginners think they need to understand brain biology to work with AI. They don\u0026rsquo;t. But the confusion about what a neuron actually does — and how it differs from a function you\u0026rsquo;d write in code — is real. And that difference matters [1][2].\nWhat\u0026rsquo;s a Neuron, Really? A neuron in AI is a computational unit. Stripped down, it\u0026rsquo;s a thing that takes inputs, does math, and produces an output. Sounds like a function, right? It kind of is. But that\u0026rsquo;s where the similarity ends.\nHere\u0026rsquo;s what happens inside a single artificial neuron [1]:\nInputs arrive from elsewhere (either the original data or outputs from previous neurons) Each input gets multiplied by a weight — a numerical value that determines how important that input is All the weighted inputs are summed together A bias gets added (a standalone adjustable number) The result passes through an activation function [3] Out comes a single output So the neuron computes something like: output = activation_function(sum(inputs × weights) + bias).\nHow That\u0026rsquo;s Different From a Function Let\u0026rsquo;s say you write a Python function:\ndef add_numbers(a, b): return a + b This function always behaves the same way. Feed it 2 and 3, get 5. Feed it the same inputs forever, you always get the same output. The logic is fixed.\nA neuron? Its behavior changes over time [2]. The weights and bias aren\u0026rsquo;t constants you hardcode. They\u0026rsquo;re parameters that get adjusted during training. The neuron starts with random weights, receives feedback on whether its outputs were right or wrong, and then adjusts those weights to do better next time. Your hardcoded function can\u0026rsquo;t do that.\nAnother difference: a regular function is deterministic and transparent. I can read the code and see exactly what it does. A neuron\u0026rsquo;s calculation is visible, sure, but why it made those weight adjustments is less obvious. When a neural network gets trained on thousands of examples, figuring out why a particular neuron ended up with weight 0.75 for input A instead of 0.73 becomes genuinely hard [2].\nWeights and Bias — The Learnable Parts This is the heart of why neurons are different from functions. Those weights and bias? They\u0026rsquo;re not hand-written. They\u0026rsquo;re learned.\nDuring training, an algorithm (typically something called backpropagation) looks at the outputs the neuron produced, compares them to what it should have produced, and nudges the weights and bias to reduce that error. Do this millions of times across billions of parameters, and suddenly the network has learned patterns in the data that humans never explicitly programmed [4].\nThe weight on an input controls how much that input influences the neuron\u0026rsquo;s output. A high weight means \u0026ldquo;this input matters a lot.\u0026rdquo; A negative weight means \u0026ldquo;if this input is high, reduce the output.\u0026rdquo;\nThe bias is an offset. It\u0026rsquo;s a way for the neuron to shift its decision boundary even when all inputs are zero. Think of it as the neuron\u0026rsquo;s baseline tendency — does it lean toward outputting 1 or 0 before any data even arrives? [4][5]\nThe Activation Function — Why Neurons Need Non-linearity Here\u0026rsquo;s a detail that surprises some beginners: if neurons just summed weighted inputs without the activation function, stacking them wouldn\u0026rsquo;t add power. You\u0026rsquo;d just end up with another linear function.\n(w1*x1 + b) → (w2*(w1*x1 + b) + b2) → still just a line The activation function breaks this. It\u0026rsquo;s a non-linear function applied to the weighted sum. Common choices:\nReLU (Rectified Linear Unit): If input is negative, output 0. Otherwise, output the input. Simple but powerful. Sigmoid: Squashes output to between 0 and 1. Good for probabilities. Tanh: Similar to sigmoid but outputs between -1 and 1. The activation function is what lets neurons combine in interesting ways to model complex relationships [3].\nOne Neuron Isn\u0026rsquo;t Useful. Many Are. A single neuron? Not very powerful. It can learn a simple linear boundary between two classes of data. But chain hundreds or thousands of them together — arrange them in layers where each layer\u0026rsquo;s output feeds into the next — and suddenly you can learn incredibly complex patterns [1].\nThat layered arrangement is the neural network. The first layer processes raw inputs. Hidden layers in the middle refine the signal. The output layer gives you your final answer. Each connection between neurons has its own weight, and during training, all of those get tweaked simultaneously [3].\nSo Why Call It a Neuron? The name comes from a loose biological inspiration. Real neurons in your brain receive signals from neighboring neurons, sum those signals, and fire or don\u0026rsquo;t fire based on whether the sum exceeds a threshold. Artificial neurons do something mathematically similar — they sum weighted inputs, add a bias, apply a non-linear function.\nThe analogy breaks down fast if you push it too far. Real neurons are vastly more complex, with different types, time-dependent dynamics, and chemical processes we don\u0026rsquo;t fully understand. But for the purposes of building AI models, the analogy is close enough to be useful [1][2].\nThe Practical Takeaway When you\u0026rsquo;re building or using a neural network, don\u0026rsquo;t think of neurons as miniature programs. Think of them as adjustable mathematical gates. Each one is simple. Dumb, even, on its own. The intelligence emerges from having many of them, learning from data, and their weights converging to useful values.\nA function is a fixed behavior written once and run forever. A neuron is a learnable component that adapts based on data. That\u0026rsquo;s the core difference, and it\u0026rsquo;s why neural networks can do things traditional programmed functions can\u0026rsquo;t.\nEnd\nSources What is an artificial neuron? | Definition from TechTarget Artificial neuron - Wikipedia Neural networks: Nodes and hidden layers | Machine Learning | Google for Developers What is a Perceptron? - Basics of Neural Networks | Towards Data Science Perceptron: Building Block of Artificial Neural Network ","permalink":"https://cloudmato.com/posts/neurons-ai-vs-functions/","title":"Neurons in AI: Not Just Functions"},{"content":"You buy a Full HD (1920×1080) monitor and check the specs. Your MacBook also outputs to a similar resolution. Yet when you start working, the text on the monitor looks noticeably softer. Not broken or unreadable — just not as sharp as what you see on your MacBook\u0026rsquo;s built-in screen. What\u0026rsquo;s going on?\nThe answer isn\u0026rsquo;t about resolution numbers. It\u0026rsquo;s about pixel density.\nThe Pixel Density Problem Here\u0026rsquo;s the thing: two displays with the same resolution at different sizes will have completely different pixel densities [1]. A 24-inch Full HD monitor has roughly 92 pixels per inch (PPI) [2]. Your MacBook Air? Try 227 PPI [1]. Your 16-inch MacBook Pro? 254 PPI [1].\nThat\u0026rsquo;s not a small difference. That\u0026rsquo;s a nearly 3× difference in how many pixels are crammed into the same physical space.\nAt 92 PPI, your eye can actually see individual pixels if you look closely. At 227+ PPI, you cannot. Apple designed macOS to be comfortable and legible at around 218 PPI for desktop displays [1]. Stray too far below that, and the OS becomes visually degraded — even if the resolution numbers sound identical.\nWhy Text Rendering Changes This is where it gets interesting. Text isn\u0026rsquo;t rendered as bitmaps anymore. It\u0026rsquo;s rendered using curves that get antialiased — smoothed to look less jagged [1].\nBut antialiasing comes in flavors, and the flavor depends on pixel density.\nOn low-PPI displays (like your 92 PPI monitor): the system used to rely on subpixel antialiasing [2]. This exploits the physical geometry of LCD screens — the red, green, and blue subpixels — to fake extra sharpness. It\u0026rsquo;s a trick that made fonts look acceptable on older, lower-density displays [2].\nOn high-PPI displays (like your MacBook): the system uses grayscale antialiasing instead [2]. It smooths fonts using shades of gray, not color tricks. At high density, this approach produces much sharper, thinner letterforms that look almost printed [3].\nHere\u0026rsquo;s the kicker: macOS Mojave removed subpixel antialiasing by default [2]. Apple decided that on Retina-class displays, the older trick was unnecessary. But on non-Retina monitors? The loss of subpixel rendering made text look noticeably softer [2].\nThe Scaling Trap Even worse, macOS scales UI elements when you use a lower-density external monitor. At 100% scaling on a 92 PPI display, everything should theoretically be sharp. But in practice, applications render at a higher logical resolution, then downscale to fit the physical pixels. This introduces subtle blurriness — especially in scrolling and animation [4].\nAt 125% or 150% scaling, the math doesn\u0026rsquo;t work out cleanly [5]. The renderer has to interpolate somewhere, and you end up with fractional pixels that blur text [5].\nWhat You Actually Need If you\u0026rsquo;re considering a new monitor for a Mac, pixel density matters far more than the raw resolution number.\nFor a 24-inch monitor, 1920×1080 gives 92 PPI — workable but not great. A 27-inch 4K display (3840×2160) gives 163 PPI [2] — noticeably sharper. Ideally, you want 160+ PPI for text-heavy work [2].\nThis is why many developers and designers pair their MacBooks with 27-inch or 32-inch 4K monitors or even 5K displays [1]. Not because higher resolution is always better, but because at those screen sizes, the pixel density climbs into the range where grayscale antialiasing produces genuinely crisp text [3].\nThe Workaround Can you improve text on your Full HD monitor without buying a new one? Slightly.\nIn macOS System Settings → General, you can enable Font Smoothing (though Apple notes this is mainly for non-Retina displays) [1]. Some users report that tweaking this setting helps, but it won\u0026rsquo;t replicate the sharpness of a high-PPI display — it\u0026rsquo;s a band-aid, not a fix.\nThe real solution is accepting that lower pixel density requires different rendering trade-offs [3]. Your Full HD monitor isn\u0026rsquo;t broken. It\u0026rsquo;s just operating in a pixel density range where the human eye starts to notice softness.\nEnd\nSources Explainer: Pixel density and display resolution – The Eclectic Light Company The subpixel-AA debacle and font rendering | MacRumors Forums The Impact of Retina Displays On Fonts – Zit Seng\u0026rsquo;s Blog macOS, blurry texts on an external Full HD monitor? | Medium Why Text Looks Blurry at 125% or 150% Display Scaling | KTC Play ","permalink":"https://cloudmato.com/posts/macbook-text-crispy-monitor-pixel-density/","title":"MacBook Text vs Monitor: Why Resolution Numbers Lie"},{"content":"I remember the first time I switched a project from CRA (which uses Webpack under the hood) to Vite. The dev server came up in under a second. I genuinely stared at the terminal for a moment, waiting for something else to happen. Nothing did. That was it — it was ready. That\u0026rsquo;s not a small improvement over Webpack. It\u0026rsquo;s a completely different experience.\nWhat Is Vite? Vite (French for \u0026ldquo;fast\u0026rdquo;, pronounced \u0026ldquo;veet\u0026rdquo;) is a frontend build tool created by Evan You — the same person who built Vue.js [1]. It launched in 2020 and has been growing fast. As of 2025, Vite logs 53 million weekly npm downloads against Webpack\u0026rsquo;s 36 million [5]. Its GitHub stars tell the same story: 78,000 vs Webpack\u0026rsquo;s 66,000 [5].\nEvery build tool has two core jobs:\nServe your code during local development with hot module replacement (HMR) Bundle and optimize the output for production Both Vite and Webpack do both. The how is completely different — and that difference is why one of them starts in milliseconds and the other makes you wait.\nWhy Webpack Gets Slow Webpack\u0026rsquo;s approach is simple but expensive: bundle everything first, then serve. Before the browser sees a single pixel, Webpack reads every file in your project, resolves every import chain, compiles everything, and outputs one big bundle.js. A medium-sized React app can take 15–30 seconds just to cold-start [1].\nThis made sense in 2014. Browsers back then couldn\u0026rsquo;t handle ES modules natively — you had to hand them one pre-processed file. Webpack was built for that world. The problem is it\u0026rsquo;s still largely working the same way, even though browsers have supported native ESM for years.\nHMR is the other pain point. When you change one file, Webpack partially rebuilds the bundle. On big projects, even small edits can take 2–5 seconds before you see the change. That feedback loop kills focus.\nThe configuration complexity is a whole other problem. I\u0026rsquo;ve inherited Webpack configs that were 400+ lines long, stacked with loaders, plugins, and environment-specific overrides that nobody fully understood anymore. According to the State of JavaScript survey, 86% of developers still use Webpack, but only 14% actually like it [8].\nHow Vite Actually Works Vite makes one fundamental bet: modern browsers understand ES modules natively, so don\u0026rsquo;t bundle during development.\nWhen the browser encounters an import in your code, it fires a request to the Vite dev server. Vite intercepts that request, transforms just that one file (TypeScript, JSX, whatever) using esbuild — a bundler written in Go that\u0026rsquo;s roughly 10–100× faster than JavaScript-based equivalents — and returns it [3].\nThere\u0026rsquo;s one catch: third-party packages in node_modules are often CommonJS, not ESM. And some libraries have hundreds of internal imports that would create hundreds of HTTP requests. So Vite pre-bundles dependencies once at startup using esbuild, converts them to ESM, and caches them. This step takes milliseconds [1]. After that, your app source files are served on demand — no bundling step at all.\nHMR in Vite stays fast as the project grows. When you change a file, Vite invalidates only that module and its direct dependents. It doesn\u0026rsquo;t touch anything else. The HMR boundary logic is precise. This is why Vite\u0026rsquo;s HMR feels instant in a 10-file project and still feels instant in a 500-file project — unlike Webpack, where HMR speed degrades as the codebase scales [3].\nVite vs Webpack — Side by Side Vite Webpack Dev server cold start \u0026lt; 1 second 15–30s (medium app) HMR speed Instant at any scale Degrades with project size Config to start ~10 lines Often 100s of lines Production bundler Rollup / Rolldown Webpack Bundle size (avg) ~130 KB ~150 KB Weekly npm downloads 53 million 36 million Best for New projects, modern stacks Legacy apps, enterprise, custom loaders Sources: [2][5]\nShopify migrated several internal tools from Webpack to Vite and went from a ~12 second startup to under 800 milliseconds [2]. That\u0026rsquo;s not optimization. That\u0026rsquo;s a different architecture.\nVite 8 and Rolldown — The Next Step Vite has always had a split personality: esbuild for development transforms, Rollup for production builds. They\u0026rsquo;re different tools with different behaviors. This occasionally caused subtle bugs — things that worked in dev but broke in production, or vice versa.\nThat\u0026rsquo;s now being addressed with Rolldown — a Rust-based bundler developed by VoidZero (Evan You\u0026rsquo;s company) built to replace both [6]. Vite 8, released in 2026, ships with Rolldown as the default production bundler, giving both dev and prod builds the same underlying engine [7].\nEarly production build results:\nExcalidraw: 22.9s → 1.4s (16× faster) [8] GitLab: 2.5 minutes → 40 seconds [8] General benchmark claims: 10–30× faster production builds vs the previous Rollup pipeline [7] The dev/prod parity problem also goes away. That\u0026rsquo;s been an underrated source of bugs for years.\nWill Vite Eventually Become Like Webpack? This is what everyone\u0026rsquo;s thinking but not saying directly. Webpack didn\u0026rsquo;t start out as the monster it became. It grew. Teams added loaders, wrote custom plugins, layered environment-specific overrides, and left legacy workarounds that nobody dared remove. Twenty config files and one senior engineer who \u0026ldquo;knows the build setup\u0026rdquo; — which every mid-sized company has experienced.\nCan Vite follow the same path? Honestly, to a degree — yes. Big teams will build big configs. SSR setups already have edge cases. Enterprise projects will push the new Environment API in Vite 6+ in ways that require deep Vite knowledge [5].\nBut there\u0026rsquo;s a structural reason it probably won\u0026rsquo;t get as bad. Webpack\u0026rsquo;s complexity was largely accidental — compatibility layers for old browsers, CommonJS-to-ESM conversions, a thousand plugins patching edge cases that native tooling has now solved. Vite sidesteps most of that by simply requiring modern environments. That cuts off an entire category of \u0026ldquo;I need a custom loader for this weird legacy thing.\u0026rdquo;\nVite\u0026rsquo;s plugin system is a deliberate superset of Rollup\u0026rsquo;s API [1]. Rather than inventing a new config format, it reuses a well-understood interface. That\u0026rsquo;s a good sign — it means ecosystem knowledge transfers rather than accumulating in isolation.\nThe complexity that Vite will accumulate will mostly be intentional — advanced users building custom SSR pipelines, multi-environment setups, or framework-level tooling. That\u0026rsquo;s a different kind of mess. You can usually still understand a Vite config you didn\u0026rsquo;t write. The same cannot be said for most inherited Webpack configs I\u0026rsquo;ve touched.\nWhether Vite in five years looks as scary as Webpack does today — I doubt it. But \u0026ldquo;not as bad as Webpack\u0026rdquo; still leaves a lot of room.\nEnd\nSources Why Vite — Vite Official Docs Vite vs. Webpack for React Apps in 2025: A Senior Engineer\u0026rsquo;s Perspective — LogRocket Vite\u0026rsquo;s Core Magic: How esbuild and Native ESM Reinvent Frontend Development — Leapcell Vite vs. Webpack: A Head-to-Head Comparison — Kinsta Webpack vs Vite: Choosing the Right Bundler for Modern Frontend Development — Syncfusion Announcing Rolldown-Vite — VoidZero Vite Version 8: Unified Rust-Based Bundler and Up to 30x Faster Builds — InfoQ Rolldown-Vite Beta: Rust-Powered Bundler Cuts Build Times by Up to 16× — Progosling ","permalink":"https://cloudmato.com/posts/vite-vs-webpack-why-vite-is-fast/","title":"Vite Explained: Why It Beats Webpack and What's Next"},{"content":"The frontend ecosystem shifts every year, but 2025-2026 felt structurally different. It wasn\u0026rsquo;t just new libraries — the underlying mental models changed. How we hydrate, bundle, structure components, and handle reactivity has moved in ways that actually affect how apps perform and how long they take to build. Here\u0026rsquo;s what\u0026rsquo;s worth paying attention to.\nThe Framework Landscape Is Fragmenting (In a Good Way) React still dominates with roughly 45% adoption [1], but calling it the default answer is getting harder to justify for every project type. Three challengers are serious now.\nSvelte 5 shipped its biggest overhaul yet: Runes. These are explicit reactive primitives — $state, $derived, $effect — that replace Svelte 4\u0026rsquo;s implicit $: label syntax [3]. The shift is significant. Runes are proper signal-based reactivity that works anywhere — inside components, in stores, in utility functions — not just at the top level of a .svelte file. Benchmarks from the Krausest js-framework test show Svelte 5 running 20–40% faster than React 19 even with the React Compiler enabled, and memory usage sitting 50% lower [4].\nReact 19 is not sitting still either. The React Compiler (v1.0, shipped October 2025) auto-memoizes components at build time, which means the useMemo / useCallback / React.memo ritual is no longer your problem [3]. Server Components moved from experimental to stable [2], and Actions API cleaned up the async mutation story significantly. React\u0026rsquo;s disadvantage is still bundle weight — the trade-off is its enormous talent pool and ecosystem.\nVue 3.6 closed most of the DX gap with React while staying tighter on bundle size. The framework convergence article at byteiota.com frames it well: \u0026ldquo;all three are chasing the same goals — less boilerplate, faster rendering, better TypeScript integration\u0026rdquo; [12].\nSolidJS deserves a mention too. It doesn\u0026rsquo;t have the adoption numbers but its fine-grained reactivity model (true signals, no VDOM) continues to influence how React and Svelte are evolving their own reactivity APIs [10].\nServer-First Is the New Default The biggest architectural shift is the move away from client-heavy SPAs toward server-first rendering with selective client hydration [13]. Two patterns are leading this.\nIsland Architecture Astro 5 is the clearest implementation. The idea: ship static HTML, then hydrate only the interactive components — \u0026ldquo;islands\u0026rdquo; — with JavaScript [5]. Astro\u0026rsquo;s client:* directive set lets you mix React, Svelte, Vue, Solid, and Lit components on the same page, each hydrated independently. In late 2024, Astro added Server Islands — dynamic server-rendered fragments that execute separately from the main page response, so one slow API call doesn\u0026rsquo;t block the entire page [5].\nFor content-heavy sites, this is the architecture worth defaulting to. You\u0026rsquo;re not shipping a 200KB React runtime to render a blog post.\nResumability Qwik takes an even more aggressive stance. Resumability rejects hydration entirely [6]. Instead of re-running components on the client to attach event listeners, Qwik serializes listener references into the HTML (on:click=\u0026quot;./chunk.js#handler\u0026quot;) and uses a single global listener to load chunks on demand [6]. The page \u0026ldquo;resumes\u0026rdquo; from the server\u0026rsquo;s serialized state — no framework code executes until the user actually interacts with something. In theory, Time to Interactive approaches zero.\nThe Astro + Qwik integration lets you get both patterns together: static by default, resumable islands for interactivity [6].\nBuild Tools: Webpack Is Dead, Long Live Rust Three serious bundlers are left standing [7]:\nTool Best For Cold Start (large app) Notes Vite 6 New projects ~2s Powered by Rolldown (Rust) replacing esbuild [8] Rspack Webpack migrations ~1.4s Built by ByteDance, webpack-compatible config [8] Turbopack Next.js shops Fastest HMR Default in Next.js 16, no other framework support [7] Vite 6.0 switched its production build engine to Rolldown, a Rust-based bundler that replaces Rollup. esbuild is being phased out for production by end of 2026 [8]. If you\u0026rsquo;re starting a new project, Vite is still the call. If you\u0026rsquo;re migrating a webpack codebase, Rspack is the least painful path — it accepts your existing webpack config and plugins.\nWebpack itself? Still running in production at thousands of companies. But no one is starting new projects on it in 2026.\nUI Libraries: shadcn/ui Changed the Model The interesting shift here isn\u0026rsquo;t a new library winning — it\u0026rsquo;s a change in how developers think about component libraries.\nshadcn/ui popularized a different model: you own the code [11]. Instead of installing a package and getting opaque components you can\u0026rsquo;t inspect or customize, shadcn/ui lets you copy components directly into your project. Full control over styling, markup, behaviour. It\u0026rsquo;s built on Radix UI primitives with Tailwind, and has become the default starting point for new React apps in 2026 [11].\nThe established players haven\u0026rsquo;t gone anywhere:\nMUI — 97,000+ GitHub stars, 4.5M weekly downloads, enterprise default [11] HeroUI (formerly NextUI) — rebranded in early 2025, growing fast [11] Chakra UI — 40,000+ stars, 700K downloads/week, still widely used [11] Tailwind CSS continues to dominate utility-first styling. There\u0026rsquo;s not much debate left there.\nDesign Patterns That Actually Stuck A few patterns crossed from \u0026ldquo;interesting idea\u0026rdquo; to \u0026ldquo;production standard\u0026rdquo; this cycle [9]:\nSignals/Fine-grained reactivity — Svelte 5 Runes, SolidJS signals, and now TC39 has a Signals proposal in progress. The VDOM may not be the long-term answer. CSS Container Queries — components that adapt to their parent container size, not the viewport. Makes component libraries genuinely reusable without media query hacks [13]. Feature-Sliced Design (FSD) — a folder structure convention that organizes code by feature, then layer (pages → widgets → features → entities → shared). Growing adoption in large React codebases because flat components/ directories become unmaintainable fast [10]. Atomic Design — breaks interfaces into atoms, molecules, and organisms. Not new, but container queries and Server Components have given it a second life [9]. TypeScript and AI-Assisted Development TypeScript is no longer optional in professional frontend work [13]. It\u0026rsquo;s the default for every major framework\u0026rsquo;s CLI. React 19, Angular 21, Vue 3.6, Svelte 5 — all ship with first-class TypeScript support out of the box. The era of debating whether to add TypeScript ended quietly somewhere around 2024.\nAI tooling (GitHub Copilot, Cursor, etc.) has genuinely changed the workflow for boilerplate. Design-to-code tools using AI can reduce concept-to-implementation time significantly for standard UI patterns [9]. But — and this is worth saying clearly — AI-generated code still needs a developer who understands what\u0026rsquo;s being generated. The mental models described in this article are what separate a developer who uses AI well from one who just pastes whatever the LLM outputs.\nThe React Compiler is its own kind of AI-adjacent automation: it analyses your code statically and inserts memoization where needed, removing a whole class of performance bugs without you doing anything [3]. That\u0026rsquo;s genuinely useful.\nWebAssembly is maturing too — computationally heavy tasks like video editing, image processing, and 3D rendering now run directly in the browser without native apps [2]. Still niche, but the gap between \u0026ldquo;web app\u0026rdquo; and \u0026ldquo;native app\u0026rdquo; is getting harder to see.\nEnd\nSources Best Frontend Frameworks 2026: Every Major JavaScript Framework You Need to Know 5 Frontend Frameworks That Will Dominate 2026 (Performance + AI) Svelte 5 Runes vs. React 19 Hooks: Which Reactivity Model Scales Better? React 19 Compiler vs Svelte 5: Latency Benchmark Results Islands Architecture — Astro Docs Astro + Qwik: Houston, we have Resumability! Vite vs Turbopack vs Rspack Benchmark [2026 Compared] Vite vs Rspack vs Turbopack: 2026 Frontend Bundler Comparison Frontend Trends and Design Patterns to Watch in 2026 5 Frontend Trends That Will Dominate 2026 — Feature-Sliced Design 5 Best React UI Libraries for 2026 (And When to Use Each) React 19 vs Vue 3.6 vs Svelte 5: 2026 Framework Convergence Frontend Development Trends 2026: What Modern Web Teams Should Focus on Now The 8 trends that will define web development in 2026 ","permalink":"https://cloudmato.com/posts/frontend-2026-frameworks-tools-design-patterns/","title":"Frontend 2026: New Frameworks, Tools \u0026 Design Patterns"},{"content":"Scan a QR code every day and never once think about those three squares. I was in that category for years. Turns out they are doing some genuinely clever engineering work — and the reason there are exactly three, not four, is more interesting than you\u0026rsquo;d expect.\nThey Have a Name: Finder Patterns The three large squares are officially called position detection patterns, though almost everyone calls them finder patterns [4]. They sit in the top-left, top-right, and bottom-left corners of every QR code — never the bottom-right. That asymmetry is intentional, and it\u0026rsquo;s the whole point.\nEach finder pattern is built from three nested squares: a solid black outer square, a white ring in the middle, a smaller black square at the centre [6]. When a scanner reads across any horizontal or vertical line through one of these patterns, it sees a very specific sequence of dark and light modules: 1:1:3:1:1 — one black, one white, three black, one white, one black [6].\nThis ratio holds regardless of how large or small the QR code is, regardless of the angle it is scanned from. That consistency is what lets a camera lock onto the pattern in milliseconds.\nWhy 1:1:3:1:1 Specifically? QR codes were invented in 1994 by Masahiro Hara at the Japanese company Denso Wave, originally for tracking car parts on assembly lines [2]. Hara\u0026rsquo;s team needed a pattern that a scanner could find instantly even when surrounded by printed text, logos, or packaging graphics. The challenge: any pattern you pick might accidentally appear in the surrounding design, causing the scanner to read the wrong thing as the code boundary.\nTheir solution was methodical. Find the pattern least likely to appear anywhere in normal printed material.\nThey actually went and surveyed real fliers, magazines, and cardboard boxes — reducing everything down to black-and-white module ratios [1]. After an exhaustive analysis, the 1:1:3:1:1 sequence came out as the one that almost never appeared naturally in printed matter. So they built the entire finder pattern around that ratio.\nThe physical look of the patterns — concentric alternating squares — was also inspired by a Go board. The contrast of black and white stones gave Hara the visual idea of working with nested alternating modules [1]. Small detail, but I like that a board game influenced one of the most scanned images on the internet.\nWhy Three Corners — Not Four? This is the more interesting part.\nThree points define a unique orientation. Four identical points do not.\nIf you have three finder patterns arranged in an L-shape (top-left, top-right, bottom-left), the scanner sees that L and immediately knows: here is which way is \u0026ldquo;up\u0026rdquo;, here is the top-left anchor, here is how the grid is oriented [3][5]. It does not matter if the QR code is upside-down, rotated 45 degrees, or printed on the side of a bottle — the L-shape resolves the orientation unambiguously.\nFour identical large squares would be a problem. The scanner would see four possible \u0026ldquo;correct\u0026rdquo; orientations and have no way to decide which one is actually right [5]. You\u0026rsquo;d need additional information elsewhere in the code to resolve the ambiguity, which defeats the purpose of having detection patterns at all.\nThe bottom-right corner instead gets a smaller alignment pattern in larger QR codes (Version 2 and above) — a 5×5 nested square whose job is different: correcting for perspective distortion when the code is on a curved surface or being read at a steep angle [6]. It helps but it isn\u0026rsquo;t a finder pattern. The asymmetry is the feature.\nThe Rest of the Code Structure Beyond the finder patterns, a QR code has several other functional zones [4]:\nTiming patterns: alternating black-and-white lines running between the finder patterns horizontally and vertically — they act as a ruler, letting the scanner measure module size and lay out the data grid precisely Format information: a strip near the finder patterns that encodes the error correction level and which data mask pattern was applied — duplicated in two places for resilience Quiet zone: the blank white border around the entire code — at minimum 4 modules wide per ISO 18004 [8]. Remove this and scanners start confusing nearby graphics for part of the code Version information: for codes at Version 7 and above, additional blocks store the version number (1–40), telling the scanner how many rows and columns to expect [4] Versions go from 1 (21×21 modules, ~25 alphanumeric characters) to 40 (177×177 modules, ~4,000+ characters). The number of alignment patterns grows as the version increases.\nError Correction: Why Damaged QR Codes Still Work You\u0026rsquo;ve definitely seen it — a QR code with a company logo stamped right in the middle that still scans fine. That is Reed-Solomon error correction, not a coincidence [7].\nThe code stores redundant data calculated from the actual payload. As long as a sufficient fraction of the modules survives, the scanner can mathematically reconstruct the original data — even if the surviving modules are scattered randomly. There are four error correction levels:\nLevel Max Recoverable Data L 7% M 15% Q 25% H 30% Level H is why you can cover up to roughly 30% of a QR code with a logo and still scan it [7]. Designers exploit this deliberately — generate the code at Level H, then place artwork over the centre. It usually works as long as the finder patterns stay intact.\nAnd that\u0026rsquo;s the thing — the finder patterns themselves are not covered by error correction. They are structural. If all three are badly damaged or obscured, no amount of Reed-Solomon math will recover the scan. The scanner won\u0026rsquo;t even find the code in the first place.\nThe three squares are not decoration. They are the entry point to everything else.\nEnd\nSources QR Code Development Story — DENSO WAVE History of QR Code — QRcode.com / DENSO WAVE QR code — Wikipedia What is QR Code Structure and How Does It Work? — Scanova QR code anatomy explained — QR Code Kit QR Code Anatomy: Finder Patterns \u0026amp; Error Correction — FileFusion QR Code Error Correction Explained — Scanova What is a QR Code Quiet Zone and Why Does It Matter? — QR Code Generator ","permalink":"https://cloudmato.com/posts/why-qr-codes-have-three-squares/","title":"Why QR Codes Have Three Squares in the Corners"},{"content":"You expose an API endpoint like /api/orders/1042. That integer tells anyone listening — a competitor, an attacker, a curious user — exactly how many orders you have. Change the number to 1041, you get the previous order. Change it to 1, you get the very first one. No auth bypass needed. The ID itself is the information leak.\nThat\u0026rsquo;s the sequential ID problem in one paragraph. UUID exists to fix it — and a few other things that matter at scale.\nWhat Exactly is a UUID? UUID stands for Universally Unique Identifier. It\u0026rsquo;s a 128-bit number, represented as 32 hexadecimal characters split into five groups with hyphens [1]:\n550e8400-e29b-41d4-a716-446655440000 The format is always 8-4-4-4-12 — that\u0026rsquo;s the count of hex characters per group, 36 total including the four hyphens [2]. The 13th character encodes the version. The 17th character encodes the variant. So that UUID above — third group starts with 4, meaning version 4 [3].\nUUIDs need no central authority to generate. No database autoincrement sequence. No cross-server coordination. Any system, anywhere, can generate a UUID and be virtually certain nobody else generated the same one [1].\nUUID Versions — They\u0026rsquo;re Not All the Same Most articles list the versions without explaining why it actually matters for your database. It does.\nVersion How it\u0026rsquo;s generated Sortable? Privacy v1 Timestamp + MAC address ✅ Yes ❌ Leaks MAC address v3 MD5 hash of namespace + name ✅ Deterministic N/A v4 Fully random (122 bits) ❌ No ✅ Good v5 SHA-1 hash of namespace + name ✅ Deterministic N/A v7 Unix timestamp + random bits ✅ Yes ✅ Good v4 is what most people use today. Random, simple, no privacy concerns. But it has a significant database performance problem that I\u0026rsquo;ll get to [3].\nv7 is what you should be using for new projects. Introduced in RFC 9562 (published May 2024), v7 combines a 48-bit Unix millisecond timestamp with 74 random bits [2]. You get time-ordering without leaking your MAC address.\nThe Collision Question \u0026ldquo;What if two systems generate the same UUID?\u0026rdquo;\nVersion 4 UUIDs have 122 bits of randomness. Total possible values: 2¹²⁸ ≈ 3.4×10³⁸. To produce a 50% chance of even one collision, you\u0026rsquo;d need to generate 2.71 quintillion v4 UUIDs [4]. Generating 1 trillion UUIDs gives a collision probability of roughly 10⁻¹⁵ [4]. That\u0026rsquo;s a one in a quadrillion chance.\nYou\u0026rsquo;re not worrying about this.\nThe Real Problem with Sequential IDs Sequential IDs — SERIAL or AUTO_INCREMENT — are simple and fast. For a small app on a single server, they\u0026rsquo;re perfectly fine. The problems show up in two specific areas.\nSecurity: Enumeration and IDOR Predictable IDs make Insecure Direct Object Reference (IDOR) attacks trivially easy [5]. IDOR is in the OWASP Top 10 — it\u0026rsquo;s one of the most commonly exploited vulnerability classes for a reason [6].\nWith sequential IDs, if I know my user ID is 4821, I can try 4820, 4819, 4818\u0026hellip; and access other users\u0026rsquo; data if your authorization checks are weak or missing at all. With UUIDs, guessing a47c3f2e-91bb-4d2e-b3f0-c6e88d3f0a1c next is computationally infeasible.\nI want to be direct about something here: UUIDs are not a substitute for proper authorization checks. If your app has IDOR vulnerabilities, fixing them at the authorization layer is the primary fix. UUIDs just raise the floor significantly [5].\nSequential IDs also leak business intelligence you probably don\u0026rsquo;t want leaking. A competitor scraping /api/invoices/1 in January and /api/invoices/8500 in December now knows your invoice volume for the year. That\u0026rsquo;s a real thing that happens.\nDistributed Systems: The Single-Node Problem Sequential IDs require centralized generation. Reliably autoincrementing an integer works on one node at a time [7].\nThe moment you have multiple database replicas writing simultaneously, data sharding across regions, microservices generating records independently, or offline-capable mobile clients — sequential IDs become a coordination nightmare. Two nodes generating the next integer will collide without a central coordinator.\nUUIDs need no coordinator. Each service, each region, each device can generate IDs independently and merge data later without conflicts [1]. That\u0026rsquo;s architecturally significant.\nWhere UUID Actually Falls Short UUIDs are not strictly better in every situation. Let me be honest about the trade-offs.\nStorage cost — 16 bytes per UUID vs 4–8 bytes for an integer. On a table with hundreds of millions of rows and multiple foreign key references, this adds up [7]. Human-readability — 42 is easier to say on a support call than 550e8400-e29b-41d4-a716-446655440000. Sequential IDs win here, always. Index performance with v4 — this is the one that bites people in production. When you use UUID v4 as a primary key, every insert lands at a random position in the B-tree index. The database has to constantly rebalance. Pages fragment. Cache locality suffers. On high write volumes with v4 UUIDs, index performance degrades noticeably compared to sequential integers [7][8].\nv7 Fixes the Performance Problem This is why v7 matters. The millisecond timestamp prefix means v7 UUIDs are time-ordered. Inserts are mostly sequential — the index grows in one direction, fragmentation stays low, cache behavior is good [2][8].\nIf you\u0026rsquo;re currently using v4 UUIDs as primary keys on high-write tables, migrating to v7 is worth considering. UUID libraries in most languages and platforms have added v7 support since RFC 9562 landed [2].\nYou get:\nThe distributed generation advantage of UUIDs The security benefits (non-guessable) Index performance close to sequential integers No MAC address leakage (unlike v1) When Sequential IDs Are Still Fine For a lot of applications, sequential IDs are the right call.\nInternal tools never exposed externally Reference/lookup tables (status codes, categories) where the ID is never user-facing Systems guaranteed to run on a single database node with no plans to shard The mistake isn\u0026rsquo;t using sequential IDs — it\u0026rsquo;s using them everywhere by default without thinking about it. Exposing /api/users/1 publicly in 2026 and not having thought about what that leaks is just careless.\nEnd\nSources Universally unique identifier — Wikipedia RFC 9562: Universally Unique IDentifiers (UUIDs) — RFC Editor UUID Versions Explained — UUIDTools.com UUID Collision Probability: Can Two UUIDs Ever Be the Same? — SecureBin Replace Sequential IDs With UUIDs to Prevent IDOR Vulnerabilities or Scraping — HackerNoon Insecure Direct Object Reference (IDOR) — OWASP Foundation UUID vs. Sequential ID as Primary Key — Baeldung Goodbye to Sequential Integers, Hello UUIDv7! — Buildkite ","permalink":"https://cloudmato.com/posts/uuid-vs-sequential-ids-explained/","title":"UUID vs Sequential IDs: What, Why, and Which to Pick"},{"content":"OOP is 50+ years old. Classes, objects, inheritance — it works, everyone knows it, almost every popular language supports it. So why are people talking about functional programming like it\u0026rsquo;s some revelation? Because OOP is great at modelling things. FP is great at modelling transformations. Most real software has both, and conflating the two is where the confusion starts.\nWhat Is Functional Programming, Actually? Not \u0026ldquo;functions inside a class.\u0026rdquo; That\u0026rsquo;s just OOP with functions.\nFunctional programming is a paradigm where you build software by composing pure functions — functions that, given the same input, always return the same output and never touch anything outside themselves [1].\nThree ideas define it:\nPure functions — no side effects, no hidden state changes. add(2, 3) always returns 5, no matter what. Immutability — once data is created, it is not modified. New data is created instead [2]. Higher-order functions — functions that take other functions as arguments or return them. map, filter, reduce are the textbook examples [3]. The rest — monads, functors, currying — are built on top of these three. You don\u0026rsquo;t need to understand all of them to benefit from FP. Most developers never touch monads and still write clean functional code every day.\nOOP Does Not Fail. It Just Has Blind Spots. Let me be direct: OOP is not bad. I use it every day. But there are specific scenarios where it actively makes your life harder.\nShared mutable state is the enemy In OOP, objects carry state. That UserService instance has a currentUser field. Your CartManager has an items array. Multiple parts of the app read and write these. And then at some point a bug appears that you cannot reproduce reliably — because the state was mutated from two places at the same time [4].\nThis is not a hypothetical. It\u0026rsquo;s the everyday reality of large OOP codebases. The shared state is hard to track, and modifying one object from two different places is a recipe for race conditions in any concurrent or async code. FP\u0026rsquo;s immutability removes this entire class of bug — you pass data in, you get data out, nothing gets mutated behind your back [5].\nTesting OOP code is more work than it should be To test a method that depends on this.db, this.cache, and this.config, you need mocks. Three mocks minimum for one test. And when the class has 20 methods and 8 dependencies, your test setup ends up longer than the actual test.\nA pure function needs none of that. Pass the input, check the output. That is the whole test [1].\nWhere Each Paradigm Actually Shines Situation Better fit Modelling real-world entities (User, Order, Car) OOP Data pipelines and transformations FP Concurrent or parallel code FP UI components with lifecycle management OOP Event processing, stream handling FP Large system with many collaborating actors OOP (with care) ETL, analytics, batch jobs FP GUI frameworks, game entities OOP This is not a \u0026ldquo;FP wins\u0026rdquo; table. OOP is the right call in plenty of situations [6].\nYes, You Can Use Both — and Most Codebases Already Do JavaScript, Python, Scala, Kotlin, Java (since Java 8), even C# — all of these are multi-paradigm languages [7]. They do not force you to pick one side.\nLook at a typical JavaScript codebase:\n// OOP for structure class UserRepository { constructor(db) { this.db = db; } async findById(id) { return this.db.query(`SELECT * FROM users WHERE id = ?`, [id]); } } // FP for transformation const formatUsers = (users) =\u0026gt; users .filter(u =\u0026gt; u.isActive) .map(u =\u0026gt; ({ id: u.id, name: u.name.trim().toLowerCase() })); UserRepository is classic OOP. formatUsers is pure FP — no side effects, same input always gives the same output. Both live in the same file, same project, no conflict [8].\nThis is not mixing paradigms out of confusion. It is using the right tool for each job.\nHow to Actually Mix Them Without Making a Mess The rule I follow: use OOP to define your structure and boundaries, use FP for the logic inside those boundaries.\nIn practice:\nClasses for services, repositories, and components that need lifecycle management. Pure functions for business logic, data transformations, and validation rules. Avoid putting heavy transformation logic inside class methods — extract it to pure functions that can be tested independently. Reach for map, filter, reduce instead of for loops that mutate a variable in place. Python example:\n# OOP for the service boundary class OrderService: def __init__(self, db): self.db = db def get_pending_orders(self, user_id): rows = self.db.fetch(user_id) return process_orders(rows) # delegate to a pure function # FP for the logic — trivial to test in isolation def process_orders(orders): return [ {**o, \u0026#34;total\u0026#34;: o[\u0026#34;price\u0026#34;] * o[\u0026#34;quantity\u0026#34;]} for o in orders if o[\u0026#34;status\u0026#34;] == \u0026#34;pending\u0026#34; ] process_orders has zero dependencies on the database, the service, or anything external. Testing it is one function call, no mocks needed [8].\nThe Languages That Already Made This Call Scala was literally designed for this — every value is an object and a function at the same time [7]. React\u0026rsquo;s shift from class components to hooks was, functionally speaking, a move toward FP for UI logic. Redux is pure FP inside a JavaScript app. Java added Stream, Optional, lambdas, and Function\u0026lt;T,R\u0026gt; in Java 8 specifically to bring FP patterns into an OOP language [3].\nThe industry already voted. Multi-paradigm is the default now, not the exception.\nOne Thing to Watch Out For Mixing paradigms is fine. Mixing them without a clear convention is how you end up with code nobody can follow. I have seen codebases where some files are fully OOP, some are fully functional, and some are both for no apparent reason. The team spends more time decoding the style than reading the logic.\nPick a convention: OOP for the shell, FP for the fill. Write it down once in your team\u0026rsquo;s guidelines. That is enough.\nEnd\nSources Functional programming vs object-oriented programming (OOP) — CircleCI Functional Programming Paradigm — GeeksforGeeks What is Functional Programming? Explained in Python, JS, and Java — Educative Functional programming vs OOP: comparing paradigms — Imaginary Cloud Functional Programming vs Object-Oriented Programming in Data Analysis — DataCamp Harnessing the Power of OOP and FP Paradigms in Software Development — DEV Community Top 5 Functional Programming Languages — Coursera Combining Object-Oriented and Functional Programming in Large Projects — DEV Community ","permalink":"https://cloudmato.com/posts/functional-programming-vs-oop-do-you-need-both/","title":"Why Use Functional Programming When OOP Exists?"},{"content":"Everyone setting up a Kubernetes cluster eventually hits the same wall: how do I actually get traffic into this thing? Then the docs mention ClusterIP, NodePort, LoadBalancer, Ingress, Gateway API, MetalLB — and it spirals. Worse, there\u0026rsquo;s a Service type called \u0026ldquo;LoadBalancer\u0026rdquo; and there are actual load balancers, and they are not the same thing. Let me go through the real options, where each one sits in the stack, and what genuinely makes sense to reach for.\nThe Two Kinds of Traffic Before picking any load balancer mechanism, it helps to know which problem you\u0026rsquo;re actually solving.\nEast-west traffic is pod-to-pod communication inside the cluster — your auth service calling your user service. Kubernetes handles this natively. Every Service gets a stable virtual IP and a DNS name, and kube-proxy running on each node programs iptables (or IPVS in larger clusters) to round-robin packets across healthy pods [1]. You do not need an external load balancer for east-west traffic at all.\nNorth-south traffic is external clients reaching your app from the internet. Kubernetes deliberately does not provision the external networking layer itself — it hands that off to a cloud provider integration or whatever you plug in [1]. This is where all the options below come in.\nThe Five Options There\u0026rsquo;s no single right answer. Each mechanism targets a different layer of the problem.\nOption OSI Layer Gets External IP? Best For ClusterIP L4 (internal) No Pod-to-pod inside the cluster NodePort L4 Via node IP (hack) Local dev and quick tests only Service type LoadBalancer L4 Yes — one per Service Small number of critical Services Ingress + Controller L7 (HTTP/HTTPS) Shared, one for all Multiple HTTP services, single IP Gateway API L4 + L7 Shared New clusters, replaces Ingress ClusterIP The default Service type [1]. Assigns a cluster-internal virtual IP that only pods inside the cluster can reach. For microservices talking to each other this is all you need. Default to ClusterIP unless you have a concrete reason to expose something externally. Most services in a real cluster don\u0026rsquo;t need an external IP — they just need other services to be able to find them.\nNodePort Opens a port in the 30000–32767 range on every node [2]. Traffic hitting that port on any node gets forwarded to the Service. Technically reaches external clients but it\u0026rsquo;s a hack — you\u0026rsquo;re exposing non-standard ports, relying on node IPs that change, and bypassing any real infrastructure-level load balancing. I\u0026rsquo;ve used NodePort on kind or minikube to quickly check if something works. Never in production.\nService type LoadBalancer When you set type: LoadBalancer on a Service, Kubernetes asks the underlying cloud provider to provision an actual load balancer and assign an external IP [1][3]. On AWS you get an NLB or ALB (depending on annotations), on GCP a regional TCP/UDP load balancer, on Azure a public IP with an Azure LB.\nThe problem: each LoadBalancer Service provisions its own separate cloud load balancer. On a 20-service application that\u0026rsquo;s 20 provisioned load balancers and 20 external IPs. Costs add up quickly, and the operational overhead is real [3]. Use this type for a small handful of services where you genuinely need a dedicated external endpoint. Not as the default pattern for every service in the cluster.\nIngress + Controller This is where most teams land for HTTP workloads. An Ingress resource defines routing rules — route /api/* to service-a, route /web/* to service-b — and an Ingress Controller (nginx, Traefik, HAProxy, etc.) implements them inside the cluster [3].\nYou still need exactly one external load balancer in front of the Ingress Controller, but then all your HTTP/HTTPS routing happens inside the cluster behind a single external IP. TLS termination happens at the controller. Much cheaper than a load balancer per service.\nTwo real limitations though. First, Ingress is HTTP-only. For TCP/UDP routing most controllers require custom annotations, which are vendor-specific and not portable [4]. Second, the Kubernetes project has frozen the Ingress API. No new features are being added [5]. It still works fine for existing setups, but new features are going into Gateway API.\nGateway API Gateway API is the proper successor to Ingress, now GA for both Layer 4 and Layer 7 as of 2026 [4][5]. It fixes the main frustrations:\nNative TCP, UDP, and gRPC support — not just HTTP/HTTPS Role-oriented design — cluster operators own the Gateway resource (infrastructure); app developers own HTTPRoute or TCPRoute (routing rules). Separate objects, separate RBAC. No more coordination nightmares [4] Portable — the spec is consistent across implementations: Envoy Gateway, Istio, NGINX, Cilium, Kong, Traefik. No more vendor-specific annotations [4] If you\u0026rsquo;re starting a new cluster today, use Gateway API. The major cloud providers support it directly. Ingress will work for years but it\u0026rsquo;s just accruing technical debt [5].\nWhat About Bare Metal? Cloud providers wire up the LoadBalancer Service plumbing for you transparently. On bare metal — self-managed VMs, an on-prem rack, a home lab running k3s — there\u0026rsquo;s no cloud provider. Your type: LoadBalancer Services will sit in \u0026lt;pending\u0026gt; state indefinitely [6].\nMetalLB is the standard fix. It gives your cluster its own IP pool and advertises those IPs either via Layer 2 (ARP) or BGP [7]. You carve out a range of IPs on your local network, configure MetalLB with an IPAddressPool, and it takes over IP assignment for LoadBalancer Services.\nThe typical bare-metal stack:\nMetalLB assigns one IP from your pool to the Ingress Controller\u0026rsquo;s Service The Ingress Controller (nginx-ingress, Traefik) handles HTTP routing and TLS Internal services stay on ClusterIP # carve out IPs for MetalLB — don\u0026#39;t reuse node IPs or DHCP-managed addresses apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: local-pool namespace: metallb-system spec: addresses: - 192.168.1.200-192.168.1.210 --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: default namespace: metallb-system MetalLB also handles failover — if the node advertising the IP goes down, it re-advertises from another node. The IP stays stable for clients [7].\nCloud Clusters: The AWS EKS Example On EKS, you install the AWS Load Balancer Controller which reconciles Kubernetes Service and Ingress objects into real AWS resources [8]. The mapping is straightforward:\nK8s Object AWS Resource What It Does Service (type LoadBalancer) AWS Network Load Balancer (NLB) L4, static IP, TCP/UDP Ingress AWS Application Load Balancer (ALB) L7, WAF, Cognito, path routing HTTPRoute (Gateway API) ALB via Gateway API controller L7, modern declarative config One thing that bit me: never modify the service.beta.kubernetes.io/aws-load-balancer-type annotation on an existing Service. If you need to change it, delete the Service and recreate it. Modifying in place causes leaked AWS resources that don\u0026rsquo;t get cleaned up [8].\nEKS Auto Mode now handles NLB provisioning automatically when you create a LoadBalancer Service — no extra controller installation needed [8]. For anything beyond basic NLB needs, the Load Balancer Controller is still the right tool.\nInside or Outside? Both. The question itself is a bit of a false choice. Production Kubernetes load balancing is always layered, not a pick-one decision:\nOutside the cluster — A cloud LB or MetalLB provides the stable external endpoint and handles raw L4 traffic At the cluster edge (inside) — An Ingress Controller or Gateway API handles HTTP routing, TLS termination, rate limiting, and path-based rules Deeper inside — ClusterIP Services handle all east-west traffic between pods, invisible to anything outside Trying to collapse all of this into one mechanism always leads to pain. One external LB per Service is expensive. Trying to do complex L7 routing directly on a cloud NLB is awkward. Treating an Ingress Controller as your L4 TCP router requires hacks. The layers exist for a reason.\nNodePort is really only for local dev or CI environments. If someone tells you to \u0026ldquo;just use NodePort in production to keep it simple\u0026rdquo; — push back. You\u0026rsquo;re bypassing infrastructure-level load balancing, locking yourself to node IPs, and opening non-standard firewall ports. It doesn\u0026rsquo;t actually simplify anything past the first week.\nEnd\nSources Services, Load Balancing, and Networking | Kubernetes Kubernetes Load Balancer: What Are the Options? | Komodor The Ultimate Guide to Kubernetes Services, LoadBalancers, and Ingress | Robusta Understanding Kubernetes Gateway API: A Modern Approach to Traffic Management | CNCF Gateway API | Kubernetes Bare-metal considerations - Ingress-Nginx Controller How to Deploy MetalLB with Nginx Ingress Controller | OneUptime Load Balancing - Amazon EKS Best Practices ","permalink":"https://cloudmato.com/posts/kubernetes-load-balancer-options/","title":"Kubernetes Load Balancers: Inside, Outside, or Both?"},{"content":"I\u0026rsquo;ve seen \u0026ldquo;zero day\u0026rdquo; used in breach headlines, movie trailers, and vendor marketing emails. Almost always it means \u0026ldquo;scary hack.\u0026rdquo; That\u0026rsquo;s not wrong exactly — but it misses the precise thing that makes a zero-day structurally different from every other attack. That precision actually matters when you\u0026rsquo;re trying to understand your risk.\nThree Terms That Are Not the Same I see these used interchangeably constantly. They\u0026rsquo;re not.\nZero-day vulnerability: a security flaw in software the vendor doesn\u0026rsquo;t know exists. No patch, no CVE, no warning. [1] Zero-day exploit: the specific code or technique an attacker builds to take advantage of that flaw. [1] Zero-day attack: the actual real-world use of that exploit against a live target. [1] The vulnerability is the hole. The exploit is the key that fits it. The attack is someone walking through the door.\nYou can have a vulnerability that no attacker has found yet. You can have an exploit that\u0026rsquo;s been written but not deployed. These carry very different risk levels and require different responses. Most articles never make this distinction, which is frustrating, because it\u0026rsquo;s the whole point.\nWhere the \u0026ldquo;Zero\u0026rdquo; Actually Comes From The name is from the vendor\u0026rsquo;s perspective. Zero days means the vendor has had zero days to prepare a fix. [2]\nNormal vulnerability discovery works like this: researcher finds bug → reports it to vendor → vendor patches → users update. Zero-days skip steps one through three entirely. By the time anyone at the company knows the flaw exists, it\u0026rsquo;s already being exploited in the wild.\nThat\u0026rsquo;s the structural difference. Not \u0026ldquo;it\u0026rsquo;s a really bad bug.\u0026rdquo; It\u0026rsquo;s a bug where the defender has had no time to respond whatsoever before the attack begins [2]. The asymmetry is total — the attacker has all the information, the defender has none.\nThe Lifecycle: From Bug to Patch A zero-day doesn\u0026rsquo;t appear from nowhere. It follows a path [4]:\nIntroduction — a developer ships a bug. Buffer overflow, authentication bypass, improper input validation. It sits dormant in the codebase. Discovery — someone finds it. An attacker, a security researcher, an intelligence agency. Exploitation — if the discoverer is malicious (or sells to someone who is), an exploit is built and deployed before the vendor knows the flaw exists. This is the zero-day window. Disclosure — the vulnerability becomes public. Either someone catches the attack in progress, a researcher independently finds it, or the vendor gets notified through responsible disclosure. Patch release — vendor ships a fix. This is the exact moment the zero-day stops being a zero-day. Patch adoption — organisations actually deploy the fix. This can take months. The average window from bug introduction to widespread patch adoption is 312 days [4]. That\u0026rsquo;s a long time for something to be silently exploited.\nWhen Exactly Does a Zero Day Stop Being a Zero Day? Precisely: the moment the vendor ships a patch and a CVE identifier is published [3].\nAfter that, it\u0026rsquo;s called an n-day vulnerability — where \u0026ldquo;n\u0026rdquo; is the number of days since the patch dropped. A week later, still an n-day. A year later, still an n-day. The clock counts up, not down.\nThe threat doesn\u0026rsquo;t go away when the patch lands. Organisations take an average of 60 to 150 days to actually deploy security updates across their environments [4]. Attackers keep exploiting the same flaw — now with full public documentation of exactly how it works, because CVE details are public. That\u0026rsquo;s sometimes worse than the original zero-day phase. The exploit gets democratised.\nZero-Day vs N-Day: The Real Difference Zero-Day N-Day Patch exists? No Yes Vendor aware? No Yes CVE published? No Yes Who uses it? Nation-states, APT groups Anyone with a PoC script Cost of exploit Millions of dollars Near zero Can defender patch? No Yes — but often doesn\u0026rsquo;t The zero-day is scarce, expensive, and typically used surgically [6]. The n-day is a commodity. Once a CVE drops and a proof-of-concept appears on GitHub, script kiddies can run the exploit within 24 hours [6]. The economics are completely different.\nStuxnet: What Burning Four Zero Days at Once Tells You Stuxnet was discovered in 2010. Widely attributed to the US and Israel, it was designed to sabotage Iran\u0026rsquo;s nuclear programme — specifically the centrifuges used to enrich uranium [7].\nIt used four separate zero-day vulnerabilities simultaneously, all targeting Windows, to spread across air-gapped networks and send malicious commands to Siemens industrial controllers that physically destroyed the centrifuges [7].\nFour zero-days in a single piece of malware was almost unheard of. Zero-days are expensive. You don\u0026rsquo;t burn four of them unless you have near-unlimited resources and an extremely high-value target. When researchers found Stuxnet, the number of zero-days used was itself evidence that this was a state-level operation. Normal malware can\u0026rsquo;t afford that.\nPegasus: Zero-Click Means You Did Nothing Wrong NSO Group\u0026rsquo;s Pegasus spyware went further than most people realise. It used iOS zero-day exploits — specifically zero-click exploits [8]. Zero-click means the victim doesn\u0026rsquo;t tap a link, doesn\u0026rsquo;t open an attachment, doesn\u0026rsquo;t do anything at all.\nYou receive an iMessage. Your phone is compromised. You never knew it happened.\niOS zero-click exploits reportedly sell for close to $10 million on the gray market [9]. Governments are the primary buyers [9]. The economics explain why most high-quality zero-days don\u0026rsquo;t end up in ransomware campaigns targeting hospitals — they\u0026rsquo;re too valuable, and too finite. Once used, a zero-day can be detected and patched.\nThe Disclosure Problem There\u0026rsquo;s genuine tension in the security community about how long vendors should get to fix something before details go public.\nGoogle\u0026rsquo;s Project Zero uses a 90+30 day policy: 90 days for the vendor to ship a patch, then 30 days for users to install it, then details go public regardless of whether the vendor is ready [10].\nThe logic: disclosure deadlines force vendors to act. Without them, vendors can delay fixes for years while users stay exposed.\nThe counter-argument: once technical details are public, every attacker in the world has a tutorial. For n-day exploitation, a published PoC is the starting gun.\nI\u0026rsquo;m not sure either approach fully solves the problem. Pegasus exploited iOS flaws faster than Apple could patch them. The journalists and activists whose phones were compromised weren\u0026rsquo;t protected by any disclosure timeline.\nDefending Against Something You Can\u0026rsquo;t See Coming Signature-based detection doesn\u0026rsquo;t work against unknown vulnerabilities. You can\u0026rsquo;t match a pattern that doesn\u0026rsquo;t exist yet.\nRealistic defences have to be behavioural:\nBehavioural monitoring — detect unusual process activity, lateral movement, and anomalous privilege escalation instead of known malware signatures Least-privilege architecture — limit what an attacker can access even after getting in Network segmentation — contain the blast radius when something does get exploited Web Application Firewalls — block anomalous request patterns before they reach the application layer Aggressive patch deployment — the moment a zero-day becomes an n-day, you have a fix available. Not deploying it fast just means becoming an easy n-day target with a published attack manual In 2024 alone, 75 zero-day vulnerabilities were exploited in the wild [5]. Microsoft had 26, Google had 11, Ivanti had 7, Apple had 5 [5]. Every single one eventually became a patched CVE — and every organisation that delayed applying that patch kept the door open long after it needed to be closed.\nThe zero-day window ends when the patch ships. The n-day problem starts immediately after.\nEnd\nSources What is a Zero Day Attack? — Fortinet Zero-day vulnerability — Wikipedia One-day, n-day, and zero-day vulnerabilities explained — Field Effect The Zero Day Vulnerability Lifecycle \u0026amp; 5 Defensive Measures — Oligo Security Hello 0-Days, My Old Friend: A 2024 Zero-Day Exploitation Analysis — Google Cloud Blog Zero-Day vs One-Day Attack: Key Differences Explained — Secure.com Stuxnet — Wikipedia Pegasus (spyware) — Wikipedia What is a zero-day exploit and why are they dangerous? — Proton VPN Vulnerability Disclosure Policy — Google Project Zero ","permalink":"https://cloudmato.com/posts/zero-day-attack-explained/","title":"Zero Day Attacks: What They Are and When They Expire"},{"content":"Ninety-four percent of developers use Git [6]. The remaining six percent are mostly in specialized industries — game studios, large finance companies — and even they are slowly migrating. Every other version control system has either died, is in hospice, or survives only in a corner niche. That\u0026rsquo;s a strange outcome for software one person wrote in about two weeks.\nThe BitKeeper Incident That Started All of This Git didn\u0026rsquo;t appear because someone sat down and thought \u0026ldquo;let me design the perfect version control system.\u0026rdquo; It appeared because someone yanked a free license away.\nThe Linux kernel — one of the most complex collaborative software projects ever built — was using BitKeeper, a proprietary distributed VCS, for free. In 2005, BitKeeper\u0026rsquo;s owner revoked that free license after a dispute with the open-source community [2]. Linus Torvalds couldn\u0026rsquo;t fall back to the existing alternatives. CVS and SVN were too slow and too centralized for Linux\u0026rsquo;s scale. Mercurial was being developed in parallel but wasn\u0026rsquo;t ready.\nSo Torvalds went offline for roughly ten days and wrote Git from scratch [2].\nThat\u0026rsquo;s the origin story. Necessity, not inspiration. Anger, actually.\nWhat Git Actually Is Git is a distributed version control system. That one word — distributed — is what separates it from everything that came before it.\nIn older systems like CVS and SVN, there\u0026rsquo;s one central server. Every developer connects to it to commit, to view history, to do anything meaningful [3]. If that server goes down, you\u0026rsquo;re stuck. If you\u0026rsquo;re on a flight, you\u0026rsquo;re stuck. If the network is slow, you already know how that goes.\nWith Git, every developer has a complete copy of the entire repository — full history, all branches, every single commit — locally on their machine [3]. You commit locally. You branch locally. You diff locally. The network only gets involved when you want to share changes with others.\nThis sounds like a minor architectural choice. It\u0026rsquo;s not. It changes how you think about development entirely.\nA few things Git genuinely gets right technically:\nBranching is cheap. In SVN, branching copies the entire directory — slow and expensive. In Git, a branch is just a pointer to a commit, so creating or deleting one is essentially free [5]. Merging actually works. Git tracks the exact ancestors of every commit, so when two branches diverge it knows what changed relative to the common base. SVN\u0026rsquo;s merging was famously painful [5]. SHA-1 content hashing. Every file, commit, and tree is identified by a cryptographic hash of its content [1]. You cannot silently corrupt data — Git will detect it. Speed by design. Most operations — log, diff, commit, branch switch — are local and therefore fast. Git was built for a project the size of the Linux kernel from day one [2]. Why SVN, Mercurial, CVS, and Perforce Lost CVS — built in the 1980s — doesn\u0026rsquo;t even support atomic commits [6]. If your commit crashes halfway through, the repository is left in a broken inconsistent state. It also has no real rename support. CVS wasn\u0026rsquo;t so much beaten by Git as it was already dying before Git existed.\nSVN fixed most of CVS\u0026rsquo;s problems and was genuinely dominant for a while. But it\u0026rsquo;s centralized, branching is expensive, and offline work is severely limited [6]. As development teams shifted to distributed models — open-source contributors across continents, multiple independent microservice teams, remote-first orgs — SVN\u0026rsquo;s assumptions started to hurt. By 2025, SVN is mostly found in legacy enterprise codebases that nobody wants to touch [6].\nMercurial is the most interesting story and the one that should make Git fans at least a little humble. Mercurial launched the same year as Git, also in response to the BitKeeper incident [2]. It\u0026rsquo;s arguably easier to use, and has a more consistent CLI. But Bitbucket dropped Mercurial support in 2020, which was effectively a death sentence [6]. Without a major hosting platform, Mercurial had no network effect. Today it sits at roughly 2% market share [6].\nPerforce (Helix Core) is the only serious alternative still alive — but only in specific verticals: game development, finance, regulated enterprise. It handles large binary files better than Git and has centralized access controls that some enterprises require. It\u0026rsquo;s expensive, proprietary, and not going anywhere fast.\nSystem Architecture Branching Offline Work Market Share 2025 Git Distributed Cheap, instant Full ~94% SVN Centralized Expensive Very limited ~5% Mercurial Distributed Good Full ~2% CVS Centralized Painful None Essentially dead Perforce Centralized Moderate Limited Niche (games/enterprise) GitHub Made the Win Permanent Git won on technical merit. GitHub made it irreversible.\nGitHub launched in 2007, just two years after Git [1]. It gave Git a social layer — pull requests, forks, code review, issue tracking. Open-source projects moved there. Companies followed. Then the entire ecosystem followed. CI/CD tools assumed GitHub. Hiring pipelines referenced GitHub profiles. Package registries linked to GitHub repos.\nBy the time Bitbucket and GitLab tried to compete, GitHub had network effects that were essentially unassailable. Mercurial being technically comparable didn\u0026rsquo;t matter once GitHub existed. The platform ate the tool.\nThe 2025 Stack Overflow Developer Survey shows GitHub as the dominant collaboration platform at 81% usage among developers [11].\nWhat\u0026rsquo;s Actually Bad About Git Here\u0026rsquo;s the part most tutorials skip, or cover with a one-line \u0026ldquo;Git has a steep learning curve\u0026rdquo; and move on.\nThe CLI is a disaster of inconsistency. git checkout used to handle both switching branches and discarding file changes — two completely unrelated operations, one command. They eventually had to split it into git switch and git restore [9]. Error messages are often cryptic. \u0026ldquo;Detached HEAD state\u0026rdquo; confuses people who\u0026rsquo;ve been using Git for years. The mental model required to understand rebase vs merge vs cherry-pick is genuinely nontrivial.\nBinary files are a genuine architectural mismatch. Git stores the full content of every version of every file. Text files compress and delta-encode beautifully. A 100MB Photoshop file or a game asset does not [9]. Large binary-heavy repos balloon in size quickly. This is the actual reason game studios stick with Perforce — not preference, but a fundamental mismatch between Git\u0026rsquo;s design and their data. Git LFS exists as a workaround, but it adds operational complexity and doesn\u0026rsquo;t fully solve the problem.\nMonorepos break down at scale. Companies running massive codebases with millions of files find that Git degrades badly. A single git pull on a 9GB repo can take six minutes [9]. Microsoft had to build their own virtual filesystem layer (VFS for Git, now Scalar) just to make Windows source development viable in Git. That\u0026rsquo;s not a ringing endorsement of scalability.\nThe \u0026ldquo;distributed\u0026rdquo; architecture collapsed into centralized in practice. Git was designed peer-to-peer. In reality, every team pushes to GitHub, which has had 48 major outages in the past 12 months — roughly one significant disruption per week, with 257 total incidents between May 2025 and April 2026 [10]. The distributed design provides no real-world resilience when everyone\u0026rsquo;s workflow is blocked by one service being down.\nAI-generated code is straining Git\u0026rsquo;s assumptions hard. GitHub saw 206% year-over-year growth in AI-generated projects in 2025 [10], and AI agents commit at a volume and frequency that humans never did. Git\u0026rsquo;s merge conflict model, pull request workflow, and history semantics were designed for humans reading and writing code at human pace. The Register reported in May 2026 that \u0026ldquo;Git is unprepared for the AI coding tsunami\u0026rdquo; [8] — and looking at how Git currently handles this volume of automated commits, that assessment is hard to argue with.\nGit won because it was the right tool at the right time, attached to the right project, and then locked in by the right platform. It\u0026rsquo;s still the right tool for most things most of the time. It\u0026rsquo;s also a 20-year-old design built for problems that have now shifted considerably.\nEnd\nSources Git — Wikipedia The History of Git: The Road to Domination — Welcome to the Jungle About Version Control — Git SCM What Is Version Control — Atlassian Git Tutorial Git vs. Other Version Control Systems — GeeksforGeeks Version Control Systems Popularity in 2025 — RhodeCode Beyond Git: The Other Version Control Systems Developers Use — Stack Overflow Blog Git Is Unprepared for the AI Coding Tsunami — The Register Why Git Is Quietly Falling Behind in 2026 — Medium GitHub Outages 2025–2026: Reliability Analysis — IncidentHub Blog 2025 Stack Overflow Developer Survey ","permalink":"https://cloudmato.com/posts/git-why-it-won-and-what-it-gets-wrong/","title":"Git: Why It Won, and What It Gets Wrong"},{"content":"The \u0026ldquo;just scale microservices\u0026rdquo; question keeps coming up whenever Spark enters the conversation. It sounds logical — you already have distributed services, just throw more at the problem. But this comparison collapses under a pretty basic question: what kind of problem are you actually solving?\nIt Is Not a Database. Not a Queue. People come to Spark expecting something like a faster database or a smarter Kafka. Neither is accurate.\nApache Spark is a unified analytics engine for large-scale data processing — designed to run on single-node machines or full clusters, handling data engineering, data science, and machine learning workloads from the same runtime. [1] In practice: you hand it a dataset — could be 50GB, could be 50TB — and it automatically splits the work across machines, runs computations in parallel, mostly in memory, and returns a result.\nThat \u0026ldquo;mostly in memory\u0026rdquo; part is the whole game. Traditional Hadoop MapReduce wrote intermediate results to disk after every single computation step. Spark keeps them in RAM where possible, avoiding that disk I/O on every round-trip. The performance difference is not marginal — Spark runs up to 100x faster than Hadoop for iterative workloads and 10x faster on disk-based jobs. [2] Yahoo benchmarked both frameworks on large-scale datasets and found Spark completing jobs up to 20 times faster than MapReduce. [11]\nIt supports Java, Scala, Python, and R. [3] Batch jobs, streaming, SQL queries, machine learning, graph processing — all from the same engine. That is why they call it \u0026ldquo;unified.\u0026rdquo; You are not stitching together five separate tools for five separate workloads.\nHow It Actually Works Spark runs on a Driver-Executor model coordinated by a Cluster Manager. [5] Three pieces:\nDriver — your application code lives here. The Driver creates a SparkContext, translates your logical plan into a physical execution plan, optimizes it into stages, and distributes tasks to workers. [6] Cluster Manager — allocates CPU and memory across the cluster. Spark supports its own Standalone mode, Hadoop YARN, Kubernetes, and Apache Mesos. [5] Executors — worker processes running on each node. They carry out the tasks, perform the actual transformations, and cache intermediate data in memory. [6] The other critical piece is RDDs — Resilient Distributed Datasets. [12] An RDD is an immutable, fault-tolerant collection of records distributed across nodes and processed in parallel. Spark tracks the full lineage of every RDD — every transformation that was applied to produce it, represented as a Directed Acyclic Graph (DAG). [4]\nIf a worker node dies mid-job, Spark does not restart from scratch. It follows the lineage graph and recomputes only the lost partitions. [12] That failure recovery is fully automatic. You write zero recovery code.\nSo Can\u0026rsquo;t You Just Scale Microservices? Here is the honest answer.\nFor most application workloads — API requests, user authentication, CRUD operations — horizontal microservice scaling is exactly the right move. More pods, load balancer in front, done. I am not arguing against that.\nThe problem appears when data volume becomes the bottleneck, not request concurrency.\nSay you want to run a fraud detection job over 6 months of transaction records — 400 million rows. Your payment microservice cannot do that. Even if you spin up 50 instances of it, each one is designed to handle one request at a time. There is no built-in mechanism to:\nSplit 400 million rows across those 50 instances Track which rows have been processed and which haven\u0026rsquo;t Handle a node failure mid-job without losing work Merge and aggregate partial results at the end You would have to build all that coordination logic yourself. Spark is that coordination logic. Already built, battle-tested, and running in production at Netflix, Uber, Amazon, and hundreds of other companies. [9]\nThere is also a thing people miss completely — scaling microservices scales request throughput, not data throughput. More instances means you can handle more concurrent users. But all those instances are still reading from the same database. That database becomes the chokepoint, not the service tier. [10] Spark sidesteps this by reading data directly from HDFS, S3, or a data lake, processing it in-place across the cluster, without routing every record through a shared transactional database.\nA research paper integrating Spark with cloud-native microservices deployed on Kubernetes found that the combined framework reduced processing latency by up to 83.1% versus monolithic deployments. [8] Microservices handled the API and routing layer. Spark handled the data computation. They worked together — not in place of each other.\nScaling Microservices Apache Spark Problem solved Request concurrency Data volume Unit of scale Service instance Data partition State between steps Stateless by design Lineage-tracked RDDs Failure recovery Restart the container Recompute lost partitions Data location Shared relational DB Distributed file system / data lake Job duration target Milliseconds per request Seconds to minutes per full dataset What Companies Are Actually Doing With It Netflix uses Kafka + Spark Streaming to process billions of events per day from viewer interactions. [9] Their pipeline drives real-time personalization, and they have reported a 10–20% improvement in viewer engagement from the precision of those recommendations. [9]\nUber runs Spark to monitor over 15 million trips daily — real-time ride requests, driver locations, surge pricing, route optimization, demand forecasting. [9] All Spark pipelines.\nNVIDIA uses Spark specifically to merge telemetry and logs from their own microservices at scale. [13] That is worth noticing: a company running microservices heavily still needs Spark to make sense of the data those services generate.\nBoth Netflix and Uber run thousands of microservices alongside Spark. Those microservices consume Spark results. They do not replace it.\nWhen You Probably Do Not Need Spark Not every data problem is a Spark problem.\nIf your dataset fits on one machine and runs in a few minutes with plain SQL or Pandas, Spark adds operational overhead with zero benefit. Cluster management, resource tuning, partition sizing, executor memory configuration — it is not a weekend setup. [7]\nA rough heuristic:\nUnder a few hundred GB, batch, no real-time requirement → a decent database with SQL is fine Hundreds of GB to TB range, regular batch jobs → Spark starts making sense TB+ or real-time streaming at scale → Spark is the standard answer for a reason [1] The cost side is also non-trivial. Without understanding partitioning and memory management, you can easily end up paying for a Spark cluster that runs slowly and expensively. [10] It rewards people who understand how it works internally — not just people who pip install pyspark.\nEnd\nSources Apache Spark — Unified Engine for large-scale data analytics What is Spark? — Introduction to Apache Spark and Analytics — AWS What Is Apache Spark? — IBM Apache Spark — Wikipedia Apache Spark Architecture 101: How Spark Works (2026) — Flexera Apache Spark Architecture: Complete Guide with Execution Flow — Medium / Towards Data Engineering Java Microservices and Big Data: Integrating Apache Spark — SpringFuse Integrating Apache Spark with Cloud-Native Microservices for Scalable Data Processing — ResearchGate 10 Powerful Apache Spark Use Cases Across Industries — upGrad How to Scale Apache Spark Clusters for Massive Datasets — Datatas Apache Spark vs Hadoop MapReduce — Feature Wise Comparison — DataFlair What Is a Resilient Distributed Dataset (RDD)? — IBM Merging Telemetry and Logs from Microservices at Scale with Apache Spark — NVIDIA Technical Blog ","permalink":"https://cloudmato.com/posts/apache-spark-vs-scaling-microservices/","title":"Apache Spark: What It Is and Why Microservices Can't Replace It"},{"content":"Nobody ever forgets their first fight with CSS centering. You Google it, paste a Stack Overflow snippet, and move on — never stopping to ask if that was the right way or just a way. In 2026, there are at least seven distinct ways to center a div, and some of them should have been buried years ago.\nHere\u0026rsquo;s all of them, ranked worst to best.\nThe Full Comparison Method Horizontal Vertical Verdict display: table-cell Yes Yes Never Negative margins Yes Yes Never abs + translate(-50%, -50%) Yes Yes Avoid inset: 0 + margin: auto Yes Yes Sometimes margin: auto Yes No Horizontal only Flexbox Yes Yes Good default CSS Grid Yes Yes Best default CSS Anchor Positioning Relative Relative Specific use 1. display: table-cell — Please Stop This is what people did before Flexbox existed. You wrap your element in a fake table, trick the browser into treating it like a \u0026lt;td\u0026gt;, and abuse vertical-align: middle.\n.wrapper { display: table; width: 100%; height: 300px; } .box { display: table-cell; vertical-align: middle; text-align: center; } Two immediate problems. First, you need an extra wrapper div just to fake the table row — that\u0026rsquo;s extra markup purely for layout. Second, you\u0026rsquo;re semantically lying to the browser. This made sense in 2012. Flexbox shipped in 2013. display: table-cell for centering has no place in 2026. [1]\nThe one exception is HTML emails, where Outlook\u0026rsquo;s rendering engine still lives in 2003 and this technique is genuinely necessary. Outside of email templates, walk away.\n2. Negative Margins — Brittle by Design The idea here is to push the element 50% down and 50% right, then pull it back by exactly half its own dimensions using negative margins.\n.box { position: absolute; top: 50%; left: 50%; width: 200px; height: 100px; margin-top: -50px; /* half of height */ margin-left: -100px; /* half of width */ } You have to hardcode the element\u0026rsquo;s dimensions. The moment content changes and the div grows or shrinks, your centering breaks. Dynamic content — which is basically every real element — makes this technique collapse immediately. This one predates even display: table-cell hacks. There is genuinely no reason to write this in 2026 [2].\n3. position: absolute + translate — The Classic Hack This was the dominant snippet of the 2015–2020 era. It solved the hardcoded-dimensions problem of negative margins by using translate, which works as a percentage of the element\u0026rsquo;s own size.\n.parent { position: relative; } .box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } It works, and it doesn\u0026rsquo;t break on dynamic content. But it\u0026rsquo;s still a hack — you\u0026rsquo;re bending position: absolute well past its intended purpose. The element is pulled out of normal flow, the parent needs a defined height, and the whole thing reads like something written by someone who knew the what but not the why. CSS-Tricks\u0026rsquo; state-of-centering piece in 2026 calls this approach \u0026ldquo;worth avoiding,\u0026rdquo; comparing it to how float-based layouts were eventually retired when something better came along [1]. I agree.\nUse this only if you\u0026rsquo;re maintaining legacy code from before 2018 and can\u0026rsquo;t touch the structure. Otherwise, there are cleaner options below.\n4. inset: 0 + margin: auto — Absolute Positioning Done Right If you have to work with position: absolute or position: fixed, this is the modern way to center inside it. No transform math, no offsets.\n.parent { position: relative; } .box { position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content; } inset: 0 is shorthand for top: 0; right: 0; bottom: 0; left: 0. With all four edges pinned to zero and margin: auto, the browser evenly distributes the leftover space on all sides — centering the element cleanly [3].\nThis is genuinely readable. It does what it says. The downside is still that position: absolute takes the element out of normal flow, so the parent needs a defined height and surrounding elements won\u0026rsquo;t interact with it naturally. Appropriate for modals, overlays, and loading spinners — not a general purpose solution.\n5. margin: auto — Honest About Its Limits .box { width: 700px; margin: 0 auto; } Everyone knows this one. It\u0026rsquo;s been in CSS forever. Set a width on a block element, add margin: auto on the horizontal axis, and it snaps to the horizontal center of its container.\nmargin: auto does nothing for vertical centering in normal flow. That\u0026rsquo;s not a bug, it\u0026rsquo;s how the spec works. For vertical auto margins, you need a flex or grid formatting context. So this is a horizontal-only tool — and a very good one for that job. Constraining a content column to the center of the page? This is exactly the right choice. Don\u0026rsquo;t try to make it do more [4].\n6. Flexbox — The Reliable Workhorse Flexbox is probably what most frontend devs reach for by reflex in 2026, and honestly, that\u0026rsquo;s fine.\n.parent { display: flex; justify-content: center; /* horizontal */ align-items: center; /* vertical */ } Two properties. Both axes. Done. The parent doesn\u0026rsquo;t need a fixed pixel height — as long as there\u0026rsquo;s available vertical space, this works. Where Flexbox genuinely shines over Grid is in mixed-alignment scenarios: center one item while right-aligning another, stretch some children while centering others, that sort of thing [5].\nThe one gotcha that catches people constantly: if the parent container has no defined height, there is no vertical space to center into, so nothing appears to shift. Set min-height: 100dvh or a fixed height and it immediately works. This single misunderstanding accounts for a huge fraction of \u0026ldquo;why isn\u0026rsquo;t my centering working\u0026rdquo; questions [6].\n7. CSS Grid — The Best Default in 2026 Two lines.\n.parent { display: grid; place-items: center; } place-items is shorthand for align-items + justify-items. One property, both axes, zero tricks. For a full-page hero, a centered card, or a loading screen, this is the cleanest option available — readable, intentional, and nearly impossible to break [1].\n/* Full viewport centered layout */ .page { display: grid; place-items: center; min-height: 100dvh; } If you need to center a group of items as a block (rather than centering each item in its own cell), swap place-items for place-content: center. Different behavior, worth knowing both.\nCSS Grid sits at 97%+ browser support in 2026 [7]. There is no production browser you\u0026rsquo;re targeting that doesn\u0026rsquo;t support this. The \u0026ldquo;browser support\u0026rdquo; excuse for avoiding Grid died around 2020.\n8. CSS Anchor Positioning — New, Specific, and Not What You Think Anchor positioning is the most exciting CSS centering addition in years, but it\u0026rsquo;s solving a specific problem — centering a floating element relative to another specific element — not general centering.\n.button { anchor-name: --my-button; } .tooltip { position: absolute; position-anchor: --my-button; bottom: anchor(top); justify-self: anchor-center; } anchor-center is a new alignment value that centers the positioned element over its named anchor element — not the viewport, not the nearest positioned ancestor, but the exact element you declare [3]. As of mid-2026, this is supported in Chrome 125+, Edge 125+, Safari 26+, and Firefox 147+ [7].\nDon\u0026rsquo;t reach for anchor positioning just to center a div on a page. It\u0026rsquo;s designed for elements that follow another element — tooltips, popovers, dropdowns, context menus. The mechanism is completely different from Grid or Flexbox and brings in a lot of complexity for something those two solve in one line. Use it when the use case actually fits.\nThe One Rule That Trips Everyone Up Doesn\u0026rsquo;t matter which technique you use — if the parent has no defined height, vertical centering produces no visible result. The container collapses to fit its content, leaving zero empty space to distribute. Every method on this list behaves the same way. Set min-height, a fixed height, or 100dvh on the parent. Then the centering works as expected [4].\nFor 99% of cases: reach for Grid with place-items: center. When you need more control over mixed-alignment children, drop down to Flexbox. For floating UI elements anchored to specific elements, anchor positioning is finally there. Everything above those three in this list is legacy — or worse, a habit from 2015 that nobody questioned.\nEnd\nSources The State of CSS Centering in 2026 | CSS-Tricks 10 Relevant Ways to Center a div — DEV Community Using CSS Anchor Positioning — MDN Web Docs How To Center a Div — The Ultimate Guide | Josh W. Comeau How to Center Any Element in CSS: 7 Methods That Always Work — freeCodeCamp The Complete Guide to Centering in CSS | Modern CSS Solutions Introducing the CSS Anchor Positioning API | Chrome for Developers ","permalink":"https://cloudmato.com/posts/center-a-div-css-2026/","title":"Every Way to Center a Div in CSS, Ranked (2026)"},{"content":"Kafka looks deceptively simple from the outside — you publish to a topic, someone reads from it. Under the hood it is a fairly intricate distributed system where several pieces have to agree on who owns what before a single byte gets delivered. I spent a good amount of time untangling this, and most articles stop at \u0026ldquo;partitions give you parallelism\u0026rdquo; without explaining the actual handshake. Let me go deeper.\nWhat a Kafka Cluster Actually Is A Kafka cluster is a group of brokers — ordinary JVM processes, each running on its own machine (or container). Every broker stores a slice of the data and knows about the rest of the cluster [1].\nTopics are the logical unit you interact with. Physically, a topic is split into partitions, and each partition is spread across multiple brokers as replicas. One of those replicas is the leader — the only one that handles reads and writes for that partition. The rest are followers that silently replicate the leader\u0026rsquo;s data [1].\nSo when people say \u0026ldquo;Kafka scales horizontally,\u0026rdquo; what they mean is: you can have 100 partitions for a topic, and those 100 partitions can be spread across N brokers — each broker handling a different slice, each consumer in your app reading a different slice in parallel.\nWho Coordinates the Cluster — ZooKeeper vs KRaft This is where a lot of old articles confuse people. Historically, Kafka outsourced its cluster coordination to ZooKeeper. Every broker raced to create an ephemeral node in ZooKeeper; the first one to succeed became the Controller broker — the one responsible for assigning partition leaders across the cluster [2].\nThat architecture is gone. Since Kafka 3.3 it has been deprecated, and since Kafka 4.0 it is simply not supported [3]. The replacement is called KRaft (Kafka Raft).\nKRaft: Kafka Runs Its Own Consensus In KRaft mode, a subset of brokers (or dedicated nodes) act as controllers and form a Raft quorum. They elect a leader among themselves using the Raft consensus algorithm — majority vote, term numbers to prevent stale leaders, and log replication to keep the quorum consistent [4].\nAll cluster metadata — topic configs, partition assignments, broker registrations — lives in a single internal Kafka topic called __cluster_metadata. The active controller leader writes events to this log; the follower controllers replay them [3]. Brokers subscribe to this metadata stream and keep their local view updated.\nWhy does this matter? Because:\nFailover is faster — no external ZooKeeper coordination round-trip [2] The metadata log itself is replicated with Kafka\u0026rsquo;s own guarantees You deploy fewer moving parts How Partition Leaders Are Elected When a broker dies, someone has to take over its partition leaderships. That job belongs to the KRaft controller.\nThe controller uses the ISR list (In-Sync Replicas) to decide who can be the new leader [5]. ISR is the set of follower replicas that are fully caught up with the leader — lagging replicas are kicked out of ISR. So if a leader fails:\nThe controller detects the broker is gone It looks at the ISR for each partition the dead broker led It picks one ISR member and promotes it to leader It writes that decision into __cluster_metadata All other brokers learn the new leader from the metadata stream Only ISR members are eligible for leader election. This is important — it guarantees the new leader has all committed data and consumers see no gaps [5].\nHow a Consumer Actually Finds the Right Broker Here is the part most people gloss over. A consumer doesn\u0026rsquo;t just magically connect to the right broker. There is a specific handshake.\nStep 1 — Bootstrap (One-Time Cluster Discovery) You configure bootstrap.servers with one or more broker addresses. This list is only for initial discovery, not for ongoing traffic [6].\nThe client picks any address from the list, opens a TCP connection, and sends a Metadata request. Any broker can answer this — it returns the full broker list and the partition-to-leader mapping for every topic the client cares about [6]. After that single request, the client has a complete map of the cluster and connects directly to the leaders it needs.\nIf your bootstrap broker dies an hour later, the client is already fine — it knows the rest of the cluster [6].\nStep 2 — Finding the Group Coordinator Consumers don\u0026rsquo;t just connect to any broker. Every consumer group is anchored to a specific Group Coordinator broker.\nThe mapping is deterministic: Kafka hashes the group.id string to one of the partitions of the internal __consumer_offsets topic, and the leader of that partition is the Group Coordinator [7]. All consumers with the same group.id end up talking to the same coordinator. Different groups are distributed across different coordinators.\nThe consumer sends a FindCoordinator request (containing its group.id) to any broker. That broker resolves the hash and replies with the coordinator\u0026rsquo;s host and port [7].\nStep 3 — JoinGroup and Partition Assignment Once every consumer in the group has found the coordinator, the rebalance protocol kicks in:\nEach consumer sends a JoinGroup request to the coordinator The coordinator designates one consumer as Group Leader — typically the first to join The coordinator sends the full member list to the Group Leader The Group Leader runs a partition assignment strategy locally and sends the result back The coordinator distributes the assignments to every member via SyncGroup responses [7][8] The Group Leader is just a temporary logical role, not a permanent node. It changes every rebalance [7].\nPartition Assignment Strategies Kafka ships with a few built-in strategies, configurable via partition.assignment.strategy [8]:\nStrategy How It Works Best For RangeAssignor Divides partitions numerically across consumers Simple, predictable splits per topic RoundRobinAssignor Circular distribution across consumers Even distribution across topics StickyAssignor Keeps prior assignments, minimises movement on rebalance Stateful consumers with warm caches CooperativeStickyAssignor Like Sticky but allows incremental rebalances (default in 3.0+) Production — avoids stop-the-world rebalances CooperativeStickyAssignor is the default since Kafka 3.0 because it does incremental rebalances — only the partitions that actually need to move are revoked, not all of them at once [8].\nWhat Consumers Can and Cannot Read One subtle thing: consumers read from the partition leader, not followers [5]. So even if a follower replica is on a broker physically closer to your consumer, the read still goes to the leader.\nThere is one exception — Follower Fetching (introduced in Kafka 2.4), where a consumer can be configured to read from the nearest replica. But by default, leader reads only.\nConsumers also cannot read data beyond the high-water mark — the latest offset that has been acknowledged by all ISR replicas [5]. This prevents consumers from reading data that could be rolled back if the leader dies before followers catch up.\nWhat Happens When a Broker Dies Mid-Consume Say a consumer is reading partition 1, and its leader broker crashes:\nThe KRaft controller detects the broker failure It picks a new leader from the ISR for partition 1 It updates __cluster_metadata The consumer\u0026rsquo;s next fetch request gets a NOT_LEADER_OR_FOLLOWER error from the old broker (or a timeout) The consumer automatically refreshes its metadata and retries the fetch — now against the new leader [6] The consumer doesn\u0026rsquo;t need manual intervention. The client library handles the retry and metadata refresh transparently. From your application code\u0026rsquo;s perspective, there might be a brief pause, but messages are not lost or duplicated (assuming proper offset commit settings).\nThe Full Picture in One Flow Bootstrap broker — only used once, to get a full cluster map Group Coordinator — handles join/sync/heartbeat for the consumer group Partition Leader — the actual broker the consumer fetches messages from These can be three different brokers, or the same one. Kafka doesn\u0026rsquo;t care.\nWhy This Design Holds Up The architecture is genuinely elegant once you see it whole. Metadata discovery is separated from coordination which is separated from data transfer. Each layer has a clear owner — KRaft for cluster state, Group Coordinator for consumer group state, Partition Leader for data.\nFailures at any layer have defined fallback paths. A dead bootstrap broker is irrelevant after startup. A dead Group Coordinator triggers a FindCoordinator retry. A dead Partition Leader triggers a metadata refresh and reconnect.\nNot simple to implement — but very clean to reason about as an operator.\nEnd\nSources Kafka Topics, Partitions, and Brokers: Core Architecture — Conduktor ZooKeeper and Apache Kafka® Explained: From Legacy to KRaft — Confluent KRaft vs ZooKeeper — Apache Kafka Official Docs KRaft Explained: How Kafka Implements Raft Consensus — Medium Understanding In-Sync Replicas (ISR) in Apache Kafka — GeeksforGeeks What is a Kafka Bootstrap Server? — Confluent Apache Kafka® Internal Architecture — Consumer Group Protocol — Confluent Developer How to Implement Kafka Consumer Assignment Strategies — OneUptime ","permalink":"https://cloudmato.com/posts/kafka-cluster-management-consumer-routing/","title":"How Kafka Manages a Cluster and Routes Consumers Right"},{"content":"Every few years the \u0026ldquo;right\u0026rdquo; way to do layout in CSS changes completely. If you started writing CSS before 2015, you probably have a small trauma response every time someone mentions clearfix. Let me walk through how we got here — and what you should actually reach for in 2026.\nThe Table Era (1990s – mid 2000s) HTML tables were never designed for layout. They existed to display tabular data. Then web designers discovered that \u0026lt;table\u0026gt;, \u0026lt;tr\u0026gt;, and \u0026lt;td\u0026gt; gave you something CSS couldn\u0026rsquo;t yet deliver: predictable column control [1].\nSo the entire web was built with nested tables. Not just one layer deep — multi-level nesting, tables inside tables inside tables. And to control spacing? A transparent 1×1 pixel GIF image (spacer.gif) with an explicit width attribute [2]. I\u0026rsquo;m not joking. That was the technique.\nThis worked because it was consistent. CSS support in the late 90s was laughably patchy. Internet Explorer and Netscape rendered the same CSS differently. Tables rendered the same everywhere. Predictability won. [3]\nTables were never meant for layout. The fact that they became the default was entirely an accident of browser inconsistency.\nBy the early 2000s, when CSS support matured and \u0026ldquo;separation of concerns\u0026rdquo; became mainstream thinking, the community collectively agreed to move on. Except the codebase your company maintains from 2004 — that one still has them.\nThe Float Era (early 2000s – ~2012) CSS float was designed to wrap text around an image — think a newspaper photo with text flowing around it. That\u0026rsquo;s it. That was the intended use [4].\nSomeone figured out that if you float multiple \u0026lt;div\u0026gt; elements left, they sit side by side. Multi-column layout achieved. And so the entire web shifted from table hacks to float hacks.\nThe problems were immediate:\nA floated element is removed from the document flow, so the parent container collapses You had to add overflow: hidden or empty \u0026lt;div class=\u0026quot;clear\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; elements to fix this The famous clearfix hack — a CSS pseudo-element trick — became a standard copy-paste snippet every developer carried around [4] /* The clearfix hack — you\u0026#39;ve seen this a thousand times */ .clearfix::after { content: \u0026#34;\u0026#34;; display: block; clear: both; } inline-block got some use too, for horizontal navigation items. It sort of worked until you discovered the whitespace issue — any space or newline between your HTML tags would render as a visible gap between the elements. The \u0026ldquo;fix\u0026rdquo; was to put your closing \u0026lt;/li\u0026gt; on the same line as the opening \u0026lt;li\u0026gt; of the next item. Genuinely miserable.\nThis era lasted longer than it should have, mostly because browser support for alternatives was slow.\nFlexbox (2012 – present) The CSS Working Group proposed the Flex layout model in 2008, published the first working draft in 2009, and then rewrote the entire spec in 2011 to remove the dependency on float/table/inline-block hacks [1].\ndisplay: flex started shipping in browsers around 2012. It took until ~2014-2015 to be safe for production without vendor prefix juggling.\nWhat Flexbox actually solved:\nVertical centering — finally. This alone justified the migration. One-axis alignment of items with gap control Items that grow and shrink proportionally to fill available space Reversing order without touching HTML .nav { display: flex; align-items: center; gap: 1rem; } That\u0026rsquo;s a navigation bar. No float. No clearfix. No spacer GIF. Just works.\nBut Flexbox is one-dimensional. It handles a row or a column — not both simultaneously. Try to build a page layout with header, sidebar, content, and footer using only Flexbox and you\u0026rsquo;ll see what I mean. You end up nesting flex containers inside flex containers, and it gets messy fast.\nCSS Grid (2017 – present) This one came from Microsoft. Engineers at Microsoft wanted a proper layout system for their browser and submitted a Grid Layout proposal to the W3C in 2012. They actually shipped an implementation in IE10 that year using the -ms- vendor prefix [1].\nThe spec was refined for years. In 2017, Chrome, Firefox, and Safari all shipped CSS Grid within weeks of each other in a rare coordinated release. It was a genuinely big moment.\nCSS Grid is two-dimensional. Rows and columns at the same time.\n.page { display: grid; grid-template-areas: \u0026#34;header header\u0026#34; \u0026#34;sidebar content\u0026#34; \u0026#34;footer footer\u0026#34;; grid-template-columns: 250px 1fr; } That\u0026rsquo;s your entire page layout in six lines. No floats. No clearfix. No nested nonsense.\nSubgrid and Container Queries (2023 – present) These two features are the current frontier. Both are now safe to use in production.\nSubgrid The classic Grid problem: you have a grid of cards. Each card has a title, body text, and a button. The titles have different lengths. The buttons in each card end up at different vertical positions. You can\u0026rsquo;t align them across cards without JavaScript hacks — because each card is its own stacking context.\nSubgrid fixes this. A child element can inherit the parent grid\u0026rsquo;s track sizing. [5]\n.card-grid { display: grid; grid-template-rows: auto 1fr auto; /* title / body / button */ } .card { display: grid; grid-row: span 3; grid-template-rows: subgrid; /* inherits parent tracks */ } Every button is now aligned. No JavaScript. No position: absolute gymnastics.\nBrowser support as of 2026: 97%+ globally [6]. Firefox shipped it in 2019, Safari in 2022, Chrome 117 in September 2023. It\u0026rsquo;s done — use it.\nContainer Queries Media queries respond to the viewport. Container queries respond to the parent container. This matters a lot when you have the same component appearing in a sidebar and in a main content area.\n.card-container { container-type: inline-size; } @container (min-width: 400px) { .card { flex-direction: row; } } Container queries are now supported across all major browsers [7]. This is the right way to build truly reusable, responsive components that don\u0026rsquo;t need to know where on the page they\u0026rsquo;ll be placed.\nThe Actual Recommendation in 2026 People still argue \u0026ldquo;Flexbox vs Grid\u0026rdquo; like it\u0026rsquo;s a competition. Use both. They solve different problems.\nUse case Tool Page structure (header, sidebar, content, footer) CSS Grid Component internals (nav items, button groups, form rows) Flexbox Cards that need cross-item alignment CSS Grid + Subgrid Responsive components with no viewport context Container Queries Actual tabular data \u0026lt;table\u0026gt; — for real data only Float for layout Never again The mental model is simple:\nGrid = you are thinking in two dimensions. You know your rows AND columns. Flexbox = you are thinking in one dimension. A row of things, or a column of things. If you catch yourself writing display: flex; flex-wrap: wrap to create a grid of cards — stop. That\u0026rsquo;s Grid\u0026rsquo;s job. Flexbox wrapping is fine for nav items that should reflow. It\u0026rsquo;s awkward for structured multi-column cards where alignment across rows matters.\nAnd please — float: left for layout is done. Has been done since 2017. If you\u0026rsquo;re still doing it, that codebase needs a refactor, not a Stack Overflow answer.\nEnd\nSources History of CSS Grid and CSS Flexbox — Medium Tables for Layout? Absurd. — The History of the Web Tableless web design — Wikipedia Clearfix: A Lesson in Web Development Evolution — CSS-Tricks CSS Subgrid — web.dev CSS Subgrid Browser Support: Full Coverage Guide — FrontendTools Container queries in 2026: Powerful, but not a silver bullet — LogRocket Blog CSS Grid vs. Flexbox 2026: When to Use Which — StudioMeyer ","permalink":"https://cloudmato.com/posts/css-layout-history-flexbox-grid-2026/","title":"CSS Layout History: Tables to Grid — What to Use in 2026"},{"content":"Everyone calls MCP \u0026ldquo;just an API calling layer for AI\u0026rdquo;. That framing is wrong — and it\u0026rsquo;s exactly why the \u0026ldquo;we already have Swagger\u0026rdquo; objection keeps coming up. Both things need unpacking.\nWhat MCP Actually Is MCP stands for Model Context Protocol. Anthropic announced it in November 2024 [1], and by December 2025 it was donated to the Linux Foundation under the Agentic AI Foundation, co-founded with Block and OpenAI [2]. That adoption speed alone is worth noting.\nThe one-liner: MCP is a standard for how AI models discover and use external tools at runtime.\nIt is built on JSON-RPC 2.0 [2]. Every message between an AI and its tools is a structured remote procedure call — not a REST endpoint, not a webhook. The wire format is always the same regardless of what the tool does or who built it.\nAn MCP server exposes exactly three kinds of things to a model [3]:\nTools — callable functions. Think get_weather(city) or create_github_issue(title, body). Resources — structured data the model can read. A file, a database row, a config object. Prompts — pre-baked templates that guide how the model should interact with the server. The model can ask the server at runtime: what can you do? The server responds with a list of capabilities. The model picks from that list. This is called ListToolsRequest [4], and it\u0026rsquo;s a core part of the protocol — not an optional feature someone bolted on.\nWhy \u0026ldquo;Protocol\u0026rdquo; and Not Just \u0026ldquo;API\u0026rdquo; An API is a surface — a set of endpoints or functions you call. A protocol is a contract about how communication happens, covering transport, message lifecycle, error format, capability negotiation, and session management.\nHTTP is a protocol. REST is an architectural style layered on top. MCP is a protocol.\nHere is specifically what MCP defines that earns it that label:\nTransport layer — three options: STDIO for local tools running as subprocesses, HTTP+SSE for remote tools, WebSocket for full-duplex interactivity [5]. Session lifecycle — a handshake where client and server negotiate capabilities once, then maintain a stateful session. Every subsequent JSON-RPC call is faster because there\u0026rsquo;s no renegotiation [5]. Capability negotiation — the server declares what version it speaks, what primitives it exposes, what it supports. The client adapts. Standardized error format — same structure, always. No custom error schemas. Compare this to a REST API. Each one invents its own auth scheme, its own pagination strategy, its own error codes, its own versioning. A developer reads the docs and writes adapter code. MCP mandates standard patterns for all of this [4].\nThe closest analogy is LSP — Language Server Protocol [2]. Your IDE doesn\u0026rsquo;t need separate plugins for \u0026ldquo;how to talk to the Python language server\u0026rdquo; vs \u0026ldquo;how to talk to the TypeScript language server\u0026rdquo;. LSP defines the conversation shape. Every language server speaks it. MCP does the same thing for AI tools.\nNotice where OpenAPI sits in this picture — inside the MCP server, not competing with it.\nThe \u0026ldquo;We Already Have Swagger\u0026rdquo; Objection This is the most common pushback. The argument: OpenAPI already describes all my endpoints. An AI can read that spec and call the API. Why add MCP on top?\nFair question. Wrong conclusion.\nOpenAPI is a documentation format written for humans. It describes your API so a developer can read it and write code against it. Descriptions assume human context. Auth patterns, pagination, error codes — however the team felt like doing them [6].\nPut an LLM in front of a raw OpenAPI spec and several things break immediately:\nThe GitHub API has over 600 endpoints [7]. Ask an LLM to pick the right one and it gets confused — too many choices, too much ambiguity in descriptions written for human developers. OpenAPI has no standardised runtime discovery. You hand the LLM a static spec file upfront. MCP\u0026rsquo;s ListToolsRequest happens live, every session, with the server controlling exactly what it exposes. Every REST API uses custom auth, custom pagination, custom error shapes. Your LLM needs bespoke adapter code for each. MCP mandates standard patterns [4]. Agents routinely misinterpret OpenAPI parameter constraints and invent fields that don\u0026rsquo;t exist [6]. Descriptions written for developers are not the same as descriptions written for agent decision-making. Here is the comparison laid flat:\nAspect OpenAPI / Swagger MCP Primary audience Human developer AI agent (LLM) Discovery Static spec file Runtime ListToolsRequest Session state Stateless HTTP Stateful session Transport options HTTP / REST STDIO, HTTP+SSE, WebSocket Auth pattern Each API decides Standardised in protocol Descriptions written for Developer reading docs Agent making tool choices Works across all tools uniformly? No — bespoke adapters Yes — single protocol They are not enemies. An MCP server can, and often does, wrap an existing REST API internally. Tools like FastMCP can auto-generate an MCP server straight from an OpenAPI spec [8]. The MCP server becomes a curated, agent-friendly façade on top of your existing API. You keep OpenAPI for developer-facing docs. You add MCP for agent-facing interaction.\nThe N×M Problem Before MCP, connecting AI assistants to external tools was an N×M problem [1]:\nN = number of AI models and assistants M = number of tools and data sources Every combination needed its own integration. Claude\u0026rsquo;s GitHub adapter wouldn\u0026rsquo;t work with Cursor. The Cursor adapter wouldn\u0026rsquo;t work with the next agent. Every team wrote glue code from scratch, separately.\nMCP turns it into N+M. Build one MCP server for GitHub — it works with any MCP client. Claude, Cursor, Windsurf, any agent that speaks the protocol. This is the actual value proposition — not \u0026ldquo;it calls APIs\u0026rdquo; but \u0026ldquo;it\u0026rsquo;s the universal connector shape so you write the adapter once.\u0026rdquo;\nOne Thing Worth Knowing You can auto-generate an MCP server from an existing OpenAPI spec in a few minutes [8]. Sounds great. But every serious writeup on this warns the same thing: prune aggressively afterward [7]. A 600-endpoint GitHub MCP server is a disaster for an LLM. A 12-tool curated one works beautifully. The generation gets you started. The curation is the actual work.\nUnder the hood, MCP is JSON-RPC calls over a pipe or an HTTP stream — not magic. But the protocol is the point: the same conversation shape everywhere, with stateful sessions, runtime discovery, and standard error handling. That\u0026rsquo;s what makes AI agents actually composable across tools and providers.\nEnd\nSources Introducing the Model Context Protocol — Anthropic Model Context Protocol — Wikipedia Model Context Protocol (MCP) an overview — Phil Schmid MCP vs APIs: What\u0026rsquo;s the Real Difference? — freeCodeCamp Architecture overview — Model Context Protocol Exposing OpenAPI as MCP Tools — Christian Posta Auto-generating MCP Servers from OpenAPI Schemas: Yay or Nay? — Neon From OpenAPI (Swagger) to MCP Servers — La Rebelion ","permalink":"https://cloudmato.com/posts/what-is-mcp-model-context-protocol/","title":"MCP Is Not Just an API Layer for AI"},{"content":"OOP has been around for fifty years. Everyone in software has taken a course on it. Most have read about SOLID, inheritance, encapsulation, polymorphism. And yet — I keep seeing the same design mistakes in codebase after codebase, from startups to enterprise projects.\nKnowing the theory is not the same as writing good OOP. Here\u0026rsquo;s where it actually goes wrong.\nThe God Object Start a project, create a UserService class. Someone adds payment logic to it. Then notification handling. Then authentication checks. Six months later: a 2000-line file that does everything, depends on everything, and breaks every time anyone touches it.\nA class should have one, and only one, reason to change. This is the Single Responsibility Principle [3]. And the God Object — a single class that accumulates excessive responsibilities — is its most direct violation [2].\nThe real problem isn\u0026rsquo;t that the class is large. It\u0026rsquo;s that it becomes impossible to test, maintain, or hand off. To unit test one method you have to mock half the application. Onboarding someone new to that class takes three hours of archaeology. Change anything in it and you\u0026rsquo;re not sure what else breaks [1].\nThe fix is splitting. If your class has methods handling completely different concerns — say, persistence and email sending — those are two classes trapped in one file.\nInheritance Is Not a Code-Sharing Tool This is the one that genuinely bothers me, because it\u0026rsquo;s often taught wrong — or at least remembered wrong.\nInheritance should model an \u0026ldquo;is-a\u0026rdquo; relationship. A Dog is an Animal. A SavingsAccount is a BankAccount. That\u0026rsquo;s what inheritance exists for [1].\nWhat I see instead: a class has five useful methods, and rather than thinking about the design, someone just extends it to get access to those methods. The result is deep inheritance hierarchies, tight coupling between semantically unrelated classes, and codebases that take thirty minutes to trace a single function call [1].\nThe classic example is Ostrich extends Bird. Bird has a fly() method. Ostriches can\u0026rsquo;t fly, so Ostrich overrides it and throws an UnsupportedOperationException. You just broke the Liskov Substitution Principle — a subclass must be substitutable for its parent without breaking the program [3][2].\nThis comes up constantly in real codebases, not just toy examples.\nFavor composition over inheritance [4]. Instead of inheriting behavior, inject it:\n// Wrong — inherits just to override and break the contract class Ostrich extends Bird { @Override public void fly() { throw new UnsupportedOperationException(\u0026#34;Ostriches can\u0026#39;t fly\u0026#34;); } } // Better — behavior is composed, not inherited class Ostrich { private MovementBehavior movement; public Ostrich() { this.movement = new RunningBehavior(); } } The hierarchy becomes flatter, the behavior becomes swappable, and you stop fighting the design every time requirements change [4].\nTreating public As the Default Encapsulation is one of the four pillars of OOP. Recite that back to any developer and they\u0026rsquo;ll nod. Then go look at their code and find that half the fields are public and there are three global variables sitting at the top of the file [1].\nIt usually isn\u0026rsquo;t deliberate. Marking something public is faster than thinking about what access level it actually needs. But as soon as something is public, other classes will access it. Now the internal state of your class is implicitly owned by the rest of the codebase and you can\u0026rsquo;t change it without breaking things.\nStart private. Promote to protected or public only when necessary. If you\u0026rsquo;re unsure whether a method needs to be public, it probably doesn\u0026rsquo;t [1].\nGlobal variables are the extreme end of this problem. Once something is global, you\u0026rsquo;ve handed its control to the entire application. Tracking down where a global gets mutated is one of the least pleasant debugging experiences in programming.\nThe Fat Interface Trap The Interface Segregation Principle is the least-discussed one in the SOLID family, and probably the one I find violated most quietly [3].\nClients should not be forced to implement interfaces they don\u0026rsquo;t use [3]. Without this discipline, you create one large IDataManager interface with read(), write(), delete(), archive(), and export(). Now every read-only service implementing it must also implement write(), delete(), archive(), and export() — even if it will never call them.\nThe result is classes full of empty method stubs or throw new NotImplementedException(). That\u0026rsquo;s a design problem wearing the costume of a solution.\nSplit the interface. IReader, IWriter, IArchiver. Each class implements only what it actually needs.\nCopy-Paste Programming This one feels harmless in the moment. Similar logic exists in two classes, it\u0026rsquo;s just a few lines, and extracting it feels like overkill.\nIt isn\u0026rsquo;t harmless [5]. One update, one missed location, one inconsistent bug fix — and the two copies diverge. Now there\u0026rsquo;s a subtle difference in behavior between two parts of the application and nobody knows why it crept in [5].\nDon\u0026rsquo;t repeat yourself. If the same logic lives in two places, it belongs in one. And if you\u0026rsquo;re copy-pasting between classes, it\u0026rsquo;s usually a sign the original design was wrong — there\u0026rsquo;s a shared concern waiting to become its own class.\nOver-Engineering the Abstraction The flip side of everything above, and worth saying directly: SOLID principles and design patterns are tools, not goals.\nI have seen codebases where a method that adds two numbers gets wrapped in a CalculationStrategy interface, a CalculationStrategyFactory, and a CalculationStrategyFactoryProvider. Not exaggerating [6].\nAbstractions should earn their existence. Over-applying design patterns without a concrete reason makes code harder to read, not easier [7]. The biggest risk of over-applying SOLID is that simple logic now requires navigating five abstract classes and three levels of dependency injection before you understand what it does [7].\nDesign for the complexity you have. Not the complexity you might theoretically encounter someday.\nQuick Reference Mistake What It Looks Like Better Approach God Object One class with 10+ responsibilities Split by Single Responsibility Inheritance abuse Extending a class to reuse code Composition or delegation LSP violation Subclass overrides method with an exception Redesign the hierarchy No encapsulation Public fields, global state everywhere Private by default Fat interface One interface with 20 methods Multiple focused interfaces Copy-paste Identical logic in multiple classes Extract to a shared component Over-abstraction Strategy factory for simple addition Add abstraction when complexity justifies it End\nSources 3 Common Object-Oriented Programming Mistakes Junior Devs Make — DEV Community OOP Pitfalls in Java – Anti-patterns You Should Avoid SOLID Design Principles Explained — DigitalOcean Composition over Inheritance — Wikipedia Anti-Patterns in OOP: What to Watch Out For — Ahmed Ashraf on Medium How the SOLID Principles Guide Object-Oriented Design — Youngjun Kim on Medium When Using SOLID Principles May Not Be Appropriate — Baeldung ","permalink":"https://cloudmato.com/posts/oop-mistakes-programmers-make/","title":"OOP Mistakes Programmers Keep Making"},{"content":"Amazon didn\u0026rsquo;t set out to build the world\u0026rsquo;s largest cloud platform. It stumbled into one while trying to stop its own engineers from reinventing the same infrastructure wheel every few months.\nThe Internal Mess That Started It All Around 2000, Amazon was building Merchant.com — a product to let third-party retailers like Target and Marks \u0026amp; Spencer spin up e-commerce stores on Amazon\u0026rsquo;s infrastructure [2]. What followed was an organizational disaster. Every team was independently building its own version of the same storage, compute, and database primitives. No shared APIs, no standard way to access anything.\nSo they did what Amazon does: they wrote a document. In the summer of 2003, at an executive offsite at Jeff Bezos\u0026rsquo; house, the leadership team ran an exercise to identify the company\u0026rsquo;s core competency. The answer was building reliable, scalable, distributed systems [2]. Andy Jassy — then a VP, now Amazon\u0026rsquo;s CEO — saw the obvious next step. He wrote the original AWS vision document, requesting 57 people to start building what he called \u0026ldquo;an operating system for the internet\u0026rdquo; [2].\nThat document became AWS.\nThe First Services (2004–2006) Here\u0026rsquo;s something most people get wrong: SQS was the first public AWS service, not S3 or EC2 [6]. Amazon Simple Queue Service launched in preview in November 2004 — over a year before storage or compute existed publicly. Makes sense if you think about it. Amazon needed a way to decouple its own internal services before it could offer anything else.\nThen came the two services that actually changed the industry:\nAmazon S3 (Simple Storage Service) — launched March 14, 2006. Object storage at scale, pay only for what you use [3] Amazon EC2 (Elastic Compute Cloud) — preview August 24, 2006; went generally available October 2008 [3] Before EC2, a startup had to buy or lease physical servers, provision them, rack them, and pray the traffic estimates weren\u0026rsquo;t wrong. After EC2, a developer with a credit card could get as much compute as they needed in minutes [1]. The economics of building software shifted permanently.\nThree years after launch, AWS had a $58 million annual run rate [3]. Small by today\u0026rsquo;s numbers, but the direction was unmistakable.\nBuilding the Real Platform (2007–2012) AWS spent these years turning \u0026ldquo;cheap compute and storage\u0026rdquo; into a proper cloud platform. The missing pieces came one by one:\nAmazon CloudFront (2008) — CDN for edge delivery worldwide [3] Amazon RDS (2009) — managed relational databases; no more babysitting MySQL on raw EC2 [3] Amazon EMR (2009) — Hadoop clusters on demand, aimed at data teams [3] Amazon VPC (2010) — Virtual Private Cloud, isolated networking inside AWS [3] Amazon DynamoDB (2012) — NoSQL at scale, born from Amazon\u0026rsquo;s own shopping cart architecture [4] Amazon Redshift (2012) — petabyte-scale data warehousing [3] The database services were particularly significant. Before RDS, companies were either paying Oracle a fortune or self-managing MySQL on raw EC2 instances and praying nothing went wrong at 2am. AWS absorbed that entire problem.\nThe Serverless Shift (2013–2016) AWS re:Invent 2014 was the moment the industry moved again. AWS Lambda launched — the idea that you don\u0026rsquo;t need to think about servers at all, just upload a function and pay per invocation [3]. Serverless as a concept didn\u0026rsquo;t exist before Lambda made it real and practical.\nThe enterprise gaps were filling in at the same time:\nAmazon Aurora (2015) — MySQL/PostgreSQL-compatible but with a custom storage engine, claimed 5× the performance of standard MySQL [3] AWS CloudTrail (2013) — audit logging, which turned out to be non-negotiable for compliance teams AWS Certificate Manager (2016) — free TLS certificates; goodbye Symantec invoices In 2015, AWS became Amazon\u0026rsquo;s most profitable business unit — more profitable than the retail operation that had put Amazon on the map in the first place [4]. That was a genuine turning point. A side project to reduce internal chaos had become the engine funding everything else.\nBy 2016, AWS revenue hit $12.2 billion [3].\nThe Platform Matures (2017–2022) By this point AWS had stopped being \u0026ldquo;cloud compute\u0026rdquo; and become something closer to a complete operating environment for enterprises. Key additions:\nAmazon SageMaker (2017) — a managed ML platform that removed the need to set up training infrastructure from scratch [5] AWS Fargate (2017) — serverless containers; Lambda but for Docker workloads Amazon EKS (2018) — managed Kubernetes, after it became clear engineers were running it on EC2 anyway AWS Ground Station (2019) — satellite communication as a managed service [3] The strategy became obvious: identify every piece of infrastructure a company might need and build a managed version of it. Databases, queues, streaming, video transcoding, IoT, ML, blockchain, even quantum computing simulation. By 2020, the count crossed 175 services [3]. They weren\u0026rsquo;t stopping.\nThe AI Push (2023–Now) The generative AI wave caught every cloud provider off-guard in terms of speed, but AWS moved quickly:\nAmazon Bedrock — a managed API layer for foundation models: Anthropic\u0026rsquo;s Claude, Meta\u0026rsquo;s Llama, Mistral, and others. Build AI apps without managing model infrastructure at all [5] Amazon SageMaker AI (2024 redesign) — rebuilt as a unified data and AI studio, cutting model training workflows from months to days [5] Amazon Nova — AWS\u0026rsquo;s own family of foundation models, announced at re:Invent 2024 [5] AWS\u0026rsquo;s AI revenue run rate crossed $15 billion in early 2026 [9]. The compound effect of 20 years of infrastructure investment is showing up clearly in these numbers.\nWhere It Stands Today Metric 2026 figure Annual revenue (2025) $128.7 billion [7] Cloud market share ~32% [8] Total services 200+ [3] Global regions 36 [3] Availability Zones 100+ [3] Countries served 245 [3] Price reductions since 2006 129 times [3] Azure is at ~23% and growing. Google Cloud holds ~11% [8]. AWS leads but the gap is narrower than five years ago — Microsoft\u0026rsquo;s OpenAI integration has pulled significant enterprise workloads toward Azure, and that competition will only get more interesting.\nWhat\u0026rsquo;s hard to fully absorb is the trajectory. A 2003 internal document requesting 57 people to build shared APIs became a business generating more revenue than most countries\u0026rsquo; GDP. And the original reason for building it was just to stop internal teams from duplicating work.\nEnd\nSources Our Origins – Amazon Web Services How AWS came to be – TechCrunch Timeline of Amazon Web Services – Wikipedia The Remarkable History of AWS – TechAhead AWS Unveils Next-Generation Amazon SageMaker – Amazon Press What Was the First AWS Service? – Peakscale AWS Statistics 2026: Revenue \u0026amp; Operating Income – ExpandedRamblings Cloud Market Share 2026: AWS vs Azure vs Google – BusinessTats AWS at 20: Inside the Rise of Amazon\u0026rsquo;s Cloud Empire – GeekWire ","permalink":"https://cloudmato.com/posts/aws-evolution-history/","title":"How AWS Went From One Service to 200+"},{"content":"Most blogs I have seen over the years run on WordPress — a database, PHP, plugins, security patches every other week. A lot of moving parts for a site that basically publishes text. Hugo is the opposite of all that. No database. No runtime. Just plain HTML files served off a CDN, generated in under a second.\nWhat is Hugo exactly — and why is it so fast? Hugo is an open-source static site generator written in Go [1]. You write content in Markdown, pick a theme, run one command, and it produces a folder of plain HTML, CSS, and JS. That folder is your entire website. Nothing runs on the server. No PHP, no Python, no Node process to keep alive.\nThe speed is not marketing. Hugo has been benchmarked generating sites with over 10,000 pages in under 10 seconds [2]. Some benchmarks put it at up to 100x faster than alternatives like Jekyll [3]. The reason is Go — compiled, concurrent by design, and Hugo uses that well.\nNo runtime = no attack surface. No database to breach, no server-side code to exploit, no forgotten plugin that stopped receiving security updates in 2021 [4].\nSetting It Up Install Hugo. On macOS:\nbrew install hugo On Ubuntu/Debian:\nsudo apt install hugo Verify:\nhugo version Stay on v0.158.0 or later [1]. If you plan on working with SCSS-heavy themes, install the extended version — it ships with Dart Sass built in [2].\nCreate your site hugo new site my-site cd my-site git init Hugo scaffolds this structure immediately:\nmy-site/ ├── archetypes/ ├── assets/ ├── content/ ├── layouts/ ├── static/ ├── themes/ └── hugo.toml content/ — your Markdown posts and pages themes/ — theme files live here static/ — copied as-is to the output (favicon, images, fonts) hugo.toml — site-wide config Write your first post hugo new content content/posts/hello-world.md Open the file and you\u0026rsquo;ll see front matter pre-filled:\n+++ title = \u0026#34;Hello World\u0026#34; date = \u0026#34;2026-06-01T22:45:10+05:30\u0026#34; draft = true +++ Set draft = false when you\u0026rsquo;re ready to publish. Hugo silently skips draft posts during a production build [1].\nPreview locally hugo server -D The -D flag includes drafts. Your site is at http://localhost:1313, hot-reloading as you save [1].\nThemes — Pick One and Stop Overthinking The official Hugo themes gallery lists over 200 options [5]. Three worth knowing:\nTheme Stars Best For PaperMod 11,000+ Developer blogs, clean reading Congo 1,300+ Personal sites, Tailwind-based Ananke 1,200+ General purpose, official starter PaperMod is the most starred Hugo theme on GitHub [6]. Fast, supports dark mode, has built-in search, table of contents, multilingual support, multiple authors. I\u0026rsquo;d start there and not look at anything else until you actually need to.\nInstall it as a git submodule:\ngit submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod Set it in hugo.toml:\ntheme = \u0026#34;PaperMod\u0026#34; PaperMod, Congo, and Ananke are all configuration-driven, not code-driven [6]. Customization is done through hugo.toml params and front matter — your changes don\u0026rsquo;t get wiped when the theme updates.\nWhere to Deploy Three genuinely free options that work well with Hugo:\nPlatform Free Bandwidth Edge CDN Auto Deploy Watch Out For Cloudflare Pages Unlimited requests Global edge Yes Shifting focus to Workers [8] GitHub Pages Unlimited (public repos) Limited Via Actions Fewer features overall [1] Netlify 100 GB/month Yes Yes Credit billing since late 2025 [8] Cloudflare Pages is where I\u0026rsquo;d put anything public-facing. Cloudflare\u0026rsquo;s global edge network means your HTML hits visitors from a node close to them [7]. The free tier doesn\u0026rsquo;t throttle bandwidth. One honest caveat: Cloudflare is increasingly pushing its Workers product over Pages [8], so the roadmap is worth watching.\nNetlify is the fastest to set up — connect a GitHub repo, it auto-detects Hugo, deploys. But Netlify changed to credit-based billing in late 2025 [8]. When you exceed the free allowance, they pause your site. Cloudflare just keeps serving.\nGitHub Pages works fine if you\u0026rsquo;re already deep in the GitHub ecosystem. No preview deployments, fewer features — but zero friction if your repo is there already [1].\nDeploying to Cloudflare Pages is five steps:\nPush your Hugo repo to GitHub or GitLab Connect the repo in the Cloudflare Pages dashboard Set build command: hugo --minify Set output directory: public Add env variable: HUGO_VERSION = 0.158.0 [7] Every push to main triggers a rebuild and deploy automatically. No babysitting.\nDay-to-Day Maintenance This is where static sites genuinely win. No server to patch. No plugin updates. No PHP version to stay compatible with. No \u0026ldquo;your WordPress installation is out of date\u0026rdquo; warning.\nThe entire content workflow is:\nhugo new content content/posts/new-post.md # write in your editor git add content/posts/new-post.md git commit -m \u0026#34;add: new post\u0026#34; git push Push to main. Site rebuilds in seconds. Done [9].\nFor non-technical collaborators, Decap CMS integrates with Hugo and provides a browser-based editor backed by Git [9]. Writers edit content in a UI, the CMS commits Markdown to the repo, Hugo builds. Nobody touches a terminal.\nHugo version upgrades are one env variable change in your hosting dashboard. No npm audit alerts, no dependency tree conflicts, no ecosystem drama.\nWhy Static at All? Static sites have been around since the web started. They went out of fashion when databases got cheap and WordPress made CMS accessible to everyone. The case for going back:\nSpeed — pre-built HTML from a CDN loads faster than anything rendered at request time [4] Security — no database, no server execution, no exploitable surface [4] Cost — genuinely free hosting for most personal and small business projects Portability — source is Markdown files in a git repo; migrate to any host in an afternoon Reliability — a static file on a CDN is very hard to bring down compared to a PHP server under traffic load [3] Hugo adds one more that matters if you have a lot of content: build time. A 1,000-page site in under a second [3]. Live reload as you write, no waiting. Large teams updating content frequently won\u0026rsquo;t be blocked by slow builds.\nIf you are running a blog, documentation site, portfolio, or marketing site that doesn\u0026rsquo;t need real-time data — there is no good reason to run a dynamic server for it.\nEnd\nSources Hugo Quick Start A Guide to Using Hugo in 2024 and 2025 — Strapi Why Hugo is the Best Static Blog Framework in 2025 — DEV Community The Resurgence of Static Sites — DillonBaird.io Hugo Themes Gallery Hugo PaperMod — GitHub Deploy a Hugo Site — Cloudflare Pages Docs Hugo Deployment: Netlify, Vercel, and Cloudflare Pages Comparison Hugo with Decap CMS: Git-Based Content Management Top Hugo Themes 2025 — Rost Glukhov ","permalink":"https://cloudmato.com/posts/build-deploy-static-site-hugo/","title":"Build and Deploy a Static Site with Hugo"},{"content":"You see it in the browser tab. You see it next to the site name in search results. You think of it as the logo of a website. So why is it called a favicon? That doesn\u0026rsquo;t sound like it has anything to do with tabs or logos at all. And you\u0026rsquo;re right — it doesn\u0026rsquo;t. The name is a relic of what the icon was originally built for, not what it does today.\nIt Was Never Meant for Tabs The name \u0026ldquo;favicon\u0026rdquo; is a squash of two words: favorite + icon [1]. As in, the icon that appears next to a website in your browser\u0026rsquo;s favourites list — what most people call bookmarks.\nThat\u0026rsquo;s the entire story behind the name. It was a bookmarks icon. Not a tab icon. Not a site logo. The browser tab use came much later, and by then the name had already stuck.\nOne Developer, One Late Night, One Sneaky Approval It\u0026rsquo;s 1999. The browser wars are in full swing. Microsoft is hammering out Internet Explorer 5 and a developer named Bharat Shyam is working on the Favorites feature [2].\nHis idea was simple: when you bookmark a site, wouldn\u0026rsquo;t it be nicer to see a tiny icon next to the URL instead of a plain text link? So he built it — a 16×16 pixel icon, loaded from a file called favicon.ico placed in the root of a website\u0026rsquo;s server [3].\nHere\u0026rsquo;s the fun part. Shyam apparently knew this addition might not get approved through normal channels, so he waited until late in the evening when a less experienced project manager was on duty — junior PM Ray Sun. He showed Sun the feature and got the code checked in [4]. That\u0026rsquo;s how favicon.ico quietly made it into IE5, released in March 1999 [5].\nHonestly, a lot of good web features probably made it through the same way.\nThe File Format Choice Because IE5 ran on Windows, Shyam used the .ico format — a Windows-native icon format that Microsoft already had full support for [2]. Drop a favicon.ico file at the root of your web server, and IE would automatically pick it up before adding the site to a user\u0026rsquo;s favorites list. No HTML tag needed. Just a convention.\nThis is also why favicon.ico at the root is still a thing in 2026. Browsers still look there by default, even though HTML gives you a proper way to declare it explicitly now.\nW3C Got Involved Quickly Within the same year, the W3C baked favicon support into the HTML 4.01 specification in December 1999 [6]. The standard way to declare one was:\n\u0026lt;link rel=\u0026#34;shortcut icon\u0026#34; href=\u0026#34;/favicon.ico\u0026#34; type=\u0026#34;image/x-icon\u0026#34;\u0026gt; Notice shortcut icon — two words. \u0026ldquo;Shortcut\u0026rdquo; was Microsoft\u0026rsquo;s terminology for bookmarks (they used \u0026ldquo;shortcuts\u0026rdquo; on the Windows desktop). So even the HTML syntax carried forward the bookmarks origin. The W3C eventually clarified that shortcut isn\u0026rsquo;t a valid keyword and rel=\u0026quot;icon\u0026quot; is the correct form [7], but you\u0026rsquo;ll still see shortcut icon all over the internet because old habits die hard.\nWhen Did It Jump to Browser Tabs? This is the part that actually makes the name feel misleading.\nIE5 only showed favicons in the favourites list and in the address bar when you were on a site. The browser tab usage came when tabbed browsing became mainstream in the early-to-mid 2000s — Firefox, Opera, Safari all picked up the favicon and started rendering it on the tab itself [2].\nAt that point, the favicon had escaped its original context entirely. It was now a persistent visual identity for every visit — bookmark or not. But nobody renamed it. \u0026ldquo;Favicon\u0026rdquo; just kept going, even though calling it a \u0026ldquo;tab icon\u0026rdquo; or \u0026ldquo;site icon\u0026rdquo; would\u0026rsquo;ve made far more sense by then.\nThe Mobile Expansion When Apple shipped the first iPhone in 2007, they introduced something called the Apple Touch Icon — a higher resolution icon that shows up when you save a webpage to your iOS home screen [2]. It looks just like an app icon.\nAndroid followed suit around 2010. Then Progressive Web Apps arrived, requiring icons in a whole manifest.json for install scenarios.\nSo now a single website is expected to maintain:\nfavicon.ico for legacy browsers A PNG favicon (typically 32×32 or 96×96) for modern browsers Apple Touch Icons (180×180 for current iOS) Web App Manifest icons (192×192, 512×512) for PWAs All of them are \u0026ldquo;the favicon\u0026rdquo; in common language. None of them are bookmarks icons anymore.\nIs the Name Misleading? Kind of, but not really — it\u0026rsquo;s just outdated.\nWhen Bharat Shyam coined \u0026ldquo;favicon\u0026rdquo; in 1999, the name was perfectly accurate. It was literally an icon for your favorites. The problem is that the function of the icon expanded massively over 25 years while the name froze in 1999. This happens a lot in tech — \u0026ldquo;wireless\u0026rdquo; used to mean radio, \u0026ldquo;desktop\u0026rdquo; still means your computer even though phones do the same work.\nThe name favicon is not misleading, it\u0026rsquo;s anachronistic. There\u0026rsquo;s a difference. Nobody was trying to confuse you — the use case just grew way past the original intent.\nThe Format Evolution Era Format Size Where 1999 .ico 16×16 Favourites list 2000s .ico / .png 16×16, 32×32 Address bar, tabs 2007+ .png 57×57 – 180×180 iOS home screen 2010s .png 192×192, 512×512 Android, PWA Now .svg Scalable All modern browsers SVG favicons are the cleanest option today — one file, infinitely scalable, works on retina screens without any pixel-hunting [2]. But .ico at the root is still not going anywhere. Browsers keep looking for it.\nThe One-File Convention That Outlived Its Reason The favicon.ico convention — drop a file at the web root and browsers find it automatically — was never formally standardised. It was just what IE5 did, and everyone else copied it [3]. To this day, a huge number of browsers will silently make a GET /favicon.ico request when you visit any page, even if no \u0026lt;link rel=\u0026quot;icon\u0026quot;\u0026gt; is declared in the HTML.\nThat\u0026rsquo;s a 1999 implementation detail that every web server in the world still handles in 2026. Pretty remarkable.\nEnd\nSources How We Got the Favicon - The History of the Web Favicon - Wikipedia A brief history of favicon - RealFaviconGenerator Inventing Favicon.ico - Take the First Favicon - Web Design Museum A Quick History of Favicon - Medium How to Add a Favicon to your Site - W3C ","permalink":"https://cloudmato.com/posts/history-of-favicon-why-called-favicon/","title":"History of Favicon: Why Is It Called Favicon?"},{"content":"TypeScript became the most-used language on GitHub in August 2025 — overtaking Python and JavaScript for the first time in over a decade [4]. 78% of professional developers now write TypeScript [5]. And yet, open almost any legacy codebase, startup MVP, or quick utility script and you\u0026rsquo;ll still find plain .js files staring back at you. That\u0026rsquo;s not laziness. There are real, structural reasons for it.\nWhat Is TypeScript, Actually? TypeScript is JavaScript with types bolted on. Microsoft built it to address a real problem — large JavaScript codebases become unmaintainable fast. When a codebase has tens of thousands of lines and dozens of contributors, you can\u0026rsquo;t know what a function expects just by reading a call site [1].\nThe core idea is static typing. In JavaScript, you find out that you passed a string where a number was expected at runtime — when the bug is already in production. TypeScript catches that at compile time, before anything runs [2].\n// TypeScript function add(a: number, b: number): number { return a + b; } add(5, \u0026#34;10\u0026#34;); // ❌ Error: Argument of type \u0026#39;string\u0026#39; is not assignable to parameter of type \u0026#39;number\u0026#39; // JavaScript function add(a, b) { return a + b; } add(5, \u0026#34;10\u0026#34;); // ✅ No error. Returns \u0026#34;510\u0026#34;. Good luck debugging. That\u0026rsquo;s the whole pitch in two code blocks.\nOne important thing: TypeScript is not a different runtime. The browser never sees a .ts file. TypeScript compiles down to plain JavaScript. At runtime, it\u0026rsquo;s all just JS anyway [1].\nWhat TypeScript Actually Gets Right Errors before they reach users This is the biggest one. A type mismatch, a missing property, a function called with wrong arguments — all of these get caught during the build step, not in a production error log at 2 AM [2].\nIDE superpowers Autocomplete in a typed codebase is actually useful. Jump to definition works. Refactoring a function name doesn\u0026rsquo;t leave orphan references all over the place. With JavaScript, your editor is mostly guessing [9].\nSelf-documenting code interface User { id: number; name: string; email: string; isAdmin: boolean; } function getUser(id: number): Promise\u0026lt;User\u0026gt; { ... } You don\u0026rsquo;t need a comment explaining what this function does or returns. The types tell you. In a team of 5+ developers, this is worth a lot [2].\nThe numbers back it up TypeScript adoption crossed 75% of active npm packages in 2025 [8] 85% of the top 1000 npm packages now ship with TypeScript type definitions [5] Developer satisfaction sits at 84.1% — highest of any language surveyed [3] A 2025 academic study found 94% of LLM-generated compilation errors were type-check failures — meaning AI coding assistants produce better output when paired with TypeScript [5] That last one is interesting. As AI writes more code, typed languages become even more valuable as a safety net.\nSo Why Do So Many Apps Still Use JavaScript? This is the honest part. There are five real reasons — not excuses.\n1. Most of the web was already written before TypeScript mattered TypeScript was released in 2012. It took years to gain serious traction. Millions of production apps were built in JavaScript before TypeScript became the default. Rewriting a working codebase is expensive and risky. Companies don\u0026rsquo;t do it unless there\u0026rsquo;s a strong business case. So those codebases stay in JavaScript — and keep getting maintained in JavaScript [10].\n2. The build step is a real cost JavaScript runs directly in the browser. TypeScript doesn\u0026rsquo;t. You need a compiler (tsc, or esbuild, or swc, or babel, or something else), a tsconfig.json, a build pipeline, and someone who understands why the build broke [6].\nFor a team building a quick internal tool or a weekend project — that overhead feels pointless. And it often is pointless at that scale [7].\n3. TypeScript\u0026rsquo;s types are optional and imperfect Here\u0026rsquo;s something people don\u0026rsquo;t talk about enough: TypeScript\u0026rsquo;s type system does not guarantee runtime safety. You can cast anything to any. You can use @ts-ignore. External API responses are unknown until you tell TypeScript what they are — and if you lie, TypeScript believes you.\nconst data = await fetch(\u0026#39;/api/user\u0026#39;).then(r =\u0026gt; r.json()) as User; // TypeScript trusts you. If the API returns something different, you still crash. For teams that don\u0026rsquo;t enforce strict mode, TypeScript projects can end up being JavaScript with extra noise [2].\n4. Not all libraries have great TypeScript support The @types/* packages on DefinitelyTyped help, but they\u0026rsquo;re maintained by volunteers and lag behind library updates. If you\u0026rsquo;re using a niche library, you might spend more time fighting missing or wrong type definitions than writing actual code [6].\n5. Small scripts don\u0026rsquo;t need this A 50-line Node.js script that reads a CSV and uploads to S3. A browser snippet that toggles a class. A serverless function that sends one email. TypeScript\u0026rsquo;s value scales with project size and team size. Below a certain threshold, it\u0026rsquo;s just noise [7].\nWhere the Line Is Scenario JS or TS? Personal script / one-off utility JavaScript Solo MVP, proof of concept JavaScript (or TS if already set up) Team project, 3+ developers TypeScript Long-lived production app TypeScript Public npm library TypeScript Framework internals (e.g. Svelte compiler) Sometimes JSDoc-annotated JS [10] Svelte is an interesting case. The Svelte team actually converted their compiler\u0026rsquo;s own source from TypeScript back to JSDoc-annotated JavaScript in 2023 — not because TypeScript is bad, but because it removed the build step from their development loop while keeping type hints via JSDoc. Svelte still fully supports TypeScript for applications built with it [10].\nTypeScript Is Winning — Just Slowly The migration isn\u0026rsquo;t theoretical. It\u0026rsquo;s happening. 40% of developers now write exclusively in TypeScript, up from 28% in 2022 [3]. Major frameworks ship TypeScript-first by default — Next.js, Nuxt, SvelteKit, Angular [8]. You essentially cannot write Angular without TypeScript at this point.\nThe AI coding wave is accelerating this. When your AI assistant generates code, you want a type system to catch the mistakes before you ship them [5].\nJavaScript will still be around for a long time — the existing web doesn\u0026rsquo;t rewrite itself. But for new projects of any real scale, the default has shifted. You\u0026rsquo;d need a specific reason to not use TypeScript now, not a reason to use it.\nEnd\nSources Difference between TypeScript and JavaScript — GeeksforGeeks TypeScript vs. JavaScript: Differences and use cases — LogRocket Blog State of JavaScript 2025: TypeScript Cementing Dominance — InfoQ TypeScript rises to the top on GitHub — InfoWorld TypeScript vs JavaScript: 73% of Devs Switched [2026] — tech-insider.org TypeScript Disadvantages Explained — FatCat Remote Why Stop Using TypeScript for Small Projects? — DEV Community The State of TypeScript Tooling in 2026 — PkgPulse TypeScript vs JavaScript — Strapi Blog Big projects are ditching TypeScript… why? — YouTube ","permalink":"https://cloudmato.com/posts/typescript-vs-javascript-why-js-still-wont-die/","title":"TypeScript Is Great. So Why Is JavaScript Still Everywhere?"},{"content":"The line between frontend developer and AI engineer is blurring fast. In 2026, the most in-demand web developers aren\u0026rsquo;t just crafting beautiful UIs—they\u0026rsquo;re wiring those UIs directly to large language models, vector databases, and autonomous agents. If you already know React, TypeScript, or Next.js, you are far closer to that future than you might think.\nWhy Frontend Developers Have a Head Start The modern AI application stack runs on TypeScript, React, and HTTP APIs—the exact tools you already use every day [1]. Frameworks like Next.js have become the default output of AI-powered UI builders, and the Vercel AI SDK is already a first-class citizen in the React ecosystem [2]. Unlike data scientists who must first learn deployment and UI, you already know how to ship products. Your challenge isn\u0026rsquo;t learning how to build—it\u0026rsquo;s learning which new primitives to build with.\nKey advantages you bring from day one:\nComponent thinking maps directly to agentic tool design and modular AI workflows API consumption (REST, GraphQL) transfers naturally to LLM API calls and streaming responses State management skills apply directly to multi-turn conversation memory TypeScript is the dominant language in production AI application layers [3] Your 6-Phase AI Roadmap The journey from frontend developer to AI-capable engineer takes roughly 6–12 months, depending on learning pace [4]. The critical principle: build something real at every phase. Watching tutorials without shipping is the single biggest time-waster in AI learning [4].\nPhase 1 — Adopt AI-Powered Dev Tools (Month 1) Before writing a single line of AI code, start using AI every day. This builds intuition for what models can and cannot do—intuition that pays dividends in every later phase [5].\nTools to adopt right now:\nGitHub Copilot – inline autocomplete for HTML, CSS, JavaScript, and all major frameworks including React, Vue, and Angular [5] Cursor – conversational code editing inside your IDE Vercel v0 – generate production-ready React + Tailwind components from plain-text descriptions [5] Codeium – the strongest free alternative for autocomplete and code suggestions [5] Important caveat: In your first month, write components manually before accepting AI suggestions. Letting AI generate large code blocks too early stunts the pattern recognition that makes you a better engineer and a better prompt writer later [1].\nPhase 2 — Integrate LLMs Into Your Apps (Months 1–3) This is where your existing skills begin to compound. Learn to call LLM APIs directly from React or Next.js.\nStart with the Vercel AI SDK. It is an open-source TypeScript toolkit that provides a unified interface across all major providers—OpenAI, Anthropic, Google Gemini, and Mistral—so you can swap models by changing just two lines of code [2]. It handles streaming, tool calling, and generative UI out of the box, making it the most frontend-native entry point into AI development [2].\nProvider Package Strengths Vercel AI SDK ai (npm) Unified API, Next.js-native, streaming UI OpenAI openai (npm) General-purpose chat and function calling Anthropic @anthropic-ai/sdk Long context, complex reasoning Google Gemini @google/generative-ai Multimodal (text + image + audio) Core skills to nail in this phase:\nStreaming responses live into the UI Structured JSON output with schema validation Multi-turn conversation with message history Basic tool/function calling Codecademy\u0026rsquo;s AI-Assisted Front-End Development path teaches you to build React apps using AI coding agents while explaining how these tools work under the hood—a useful companion for this phase [6].\nPhase 3 — Master Prompt Engineering (Months 2–4) Prompt engineering is no longer the ceiling of AI expertise—it is the floor [7]. Every AI engineer needs it, and without it, every downstream phase becomes harder.\nFive core techniques to internalize:\nSystem prompts – define the model\u0026rsquo;s persona, constraints, and output format before any user input Few-shot examples – show 2–5 example input/output pairs to reliably guide behavior Chain-of-thought (CoT) – instruct the model to reason step-by-step before delivering a final answer Structured output – use JSON schemas or Zod validation to guarantee parseable responses every time Temperature control – tune creativity vs. determinism depending on the use case (creative copy vs. code generation) Build a small real project—a content generator, a smart form, a code reviewer—while practicing these techniques. Learning only sticks when you build at the same time [4].\nPhase 4 — Build RAG Systems (Months 3–6) Retrieval-Augmented Generation (RAG) lets your AI app answer questions grounded in your own data—product docs, knowledge bases, support tickets—rather than the model\u0026rsquo;s static training data. It is one of the most consistently high-value skills in the 2026 job market [7].\nFrontend-friendly vector database options:\nSupabase + pgvector – PostgreSQL-based vector store; familiar if you already use Supabase Pinecone – fully managed, clean REST API, minimal infrastructure Upstash Vector – serverless and edge-compatible, pairs well with Vercel deployments The skill shift in 2026 is from basic RAG (search once, summarize) to agentic RAG (search iteratively, validate sources, combine findings) [7]. Basic RAG now is the foundation for Phase 5.\nPhase 5 — AI Agents and the MCP Protocol (Months 5–9) AI agents are applications where the model decides which tools to call and in what order, rather than following a fixed execution path. This is the fastest-growing area of AI engineering in 2026 [8].\nKey concepts to build:\nTool calling – give the model access to typed functions (search, calculator, database queries, API calls) Agentic loops – the model calls a tool, observes the result, then decides the next step Multi-agent coordination – specialized agents collaborating on a shared task The MCP Protocol is now the universal standard for connecting AI agents to tools and data sources. Donated to the Linux Foundation in late 2025, it was rapidly adopted by OpenAI, Google, Microsoft, and Amazon within months [7]. For frontend developers, MCP means your Next.js app can expose structured tool interfaces to any LLM using one standardized protocol—no more bespoke integrations per provider.\nFrontend Masters\u0026rsquo; Coding with AI path covers the complete agentic loop, model trade-offs, skills, hooks, and building agent teams—exactly aligned with what hiring managers screen for today [9].\nPhase 6 (Optional) — Python and ML Foundations (Months 6–12) If your goal is to move deeper into AI engineering—fine-tuning models, working with embeddings at scale, or joining a dedicated ML team—Python becomes essential. It covers approximately 90% of work in AI engineering and data science roles [4].\nWhere to invest Python time:\nnumpy and pandas for data wrangling transformers (Hugging Face) for open-source model access LangChain or LlamaIndex for production RAG and agent pipeline orchestration This phase is optional for frontend developers who want to stay in the application layer. Skipping it does not limit you; most high-value AI product work in 2026 is in TypeScript.\nTop Learning Platforms in 2026 Platform Course / Path Best For Frontend Masters Coding with AI Agents, Claude Code, LLM workflows Codecademy AI-Assisted Front-End Dev React + AI tools from scratch [6] DataCamp AI Learning Roadmap 2026 Broad AI fundamentals and Python [4] Coursera GenAI for Front-End Developers GenAI concepts for web developers Dataquest AI Engineer Roadmap Full stack → AI engineer transition [3] Capgemini Academy GenAI for Front-End Devs Ethics, Copilot, enterprise AI use Skills That Get You Hired in 2026 Recruiters screening AI-capable frontend developers in 2026 are working from a very specific checklist [7] [8]:\nLLM API integration with at least one major provider (OpenAI, Anthropic, or Gemini) Vercel AI SDK or equivalent unified-interface tooling Prompt engineering with structured output and few-shot techniques RAG system design including chunking strategy and re-ranking Agent orchestration with tool use and multi-step agentic loops MCP protocol awareness and practical implementation Eval literacy – designing and running model evaluations to measure quality Safety and guardrails – scoping agents so they cannot take unauthorized actions The biggest skill gap in the market today isn\u0026rsquo;t model knowledge or math—it\u0026rsquo;s agentic engineering: designing and operating AI agents that reliably survive in production [8]. That single capability is what separates developers who consumed tutorials from those who have shipped real AI products.\nStart with the tools you use every day, ship something small in month one, and let each phase compound on the last. The path from React developer to production-ready AI engineer in 2026 has never been shorter—or more worth taking.\nSources Learn Frontend Development in 2026 | ASSIST Software AI SDK by Vercel – Introduction AI Engineer Roadmap 2026: Skills for Full-Stack Developers | Imaginary Cloud A Realistic Roadmap to Start an AI Career in 2026 | Towards Data Science 7 Best AI Tools for Frontend Development in 2026 | eesel AI AI-Assisted Front-End Development | Codecademy AI Developer Hiring 2026: Skills That Actually Matter | Digital Applied Agentic AI Skills Required for Engineers \u0026amp; Developers 2026 | NovelVista Coding with AI Learning Path | Frontend Masters ","permalink":"https://cloudmato.com/posts/ai-learning-path-frontend-developers-2026/","title":"AI Learning Path for Frontend Developers in 2026"},{"content":"JavaScript once had no built-in way to split code across files — a limitation that spawned an entire ecosystem of competing module formats. Today, developers encounter require(), import, .mjs, .cjs, AMD, and UMD, often all in the same project. This guide demystifies every module system, explains when to use each, and maps out the clear path forward.\nWhy JavaScript Needed Module Systems In the early days of the web, JavaScript was a scripting language meant for simple page interactions. As applications grew, developers stuffed everything into global variables — leading to naming collisions and unmaintainable \u0026ldquo;spaghetti\u0026rdquo; code [1]. The community responded by inventing module patterns outside the language itself: first Immediately Invoked Function Expressions (IIFEs) to create private scopes, then formal module specifications like AMD and CommonJS. Only in 2015 did JavaScript finally gain a native module system via the ES6 specification [6].\nCommonJS (CJS) — The Node.js Workhorse CommonJS was introduced in 2009 as the module system for server-side JavaScript, and it remains the default module format in Node.js to this day [3].\nSyntax // Exporting const greet = (name) =\u0026gt; `Hello, ${name}!`; module.exports = { greet }; // Importing const { greet } = require(\u0026#39;./greet\u0026#39;); console.log(greet(\u0026#39;World\u0026#39;)); Key Characteristics Synchronous loading — require() blocks execution until the file is fully loaded, which is fine on a server but problematic in browsers [2]. Dynamic by nature — you can call require() inside if blocks, loops, or functions at runtime [1]. module.exports / exports — the exported value is a plain JavaScript object, assigned at runtime. No tree-shaking — because exports are determined at runtime, bundlers cannot statically determine which parts are unused [4]. CommonJS is still the right choice when maintaining existing Node.js codebases or working with packages that haven\u0026rsquo;t shipped an ESM build [2].\nAMD — Asynchronous Module Definition AMD emerged around 2011 specifically to solve browser performance: unlike CommonJS, it loads dependencies asynchronously so the page doesn\u0026rsquo;t freeze [5].\n// AMD define + require via RequireJS define([\u0026#39;dependency\u0026#39;], function(dep) { return { hello: () =\u0026gt; dep.greet() }; }); require([\u0026#39;myModule\u0026#39;], function(mod) { mod.hello(); }); AMD was popularised by the RequireJS loader. However, with ES Modules now providing native async loading in every browser, AMD is considered largely obsolete for new projects [6].\nUMD — Universal Module Definition In 2011, UMD arrived as a compatibility shim to bridge the gap between CommonJS (Node.js), AMD (browsers), and plain global scripts [5].\n(function(root, factory) { if (typeof define === \u0026#39;function\u0026#39; \u0026amp;\u0026amp; define.amd) { define([], factory); // AMD } else if (typeof module === \u0026#39;object\u0026#39; \u0026amp;\u0026amp; module.exports) { module.exports = factory(); // CommonJS } else { root.myLib = factory(); // Global variable } }(this, function() { return { version: \u0026#39;1.0\u0026#39; }; })); Major libraries like Lodash, Underscore.js, Backbone.js, and Moment.js adopted UMD to be universally consumable [6]. Today, UMD is practically obsolete because ES Modules are natively supported everywhere — but you\u0026rsquo;ll still encounter it in older packages on npm.\nES Modules (ESM) — The Modern Standard Introduced in ES2015 (ES6) and now supported natively in all modern browsers and Node.js ≥ 12, ESM is the official, standardised module system for JavaScript [4].\nSyntax // Named exports export const add = (a, b) =\u0026gt; a + b; export const PI = 3.14159; // Default export export default class Calculator { /* ... */ } // Importing import Calculator, { add, PI } from \u0026#39;./math.js\u0026#39;; Key Characteristics Static analysis — import and export statements must be at the top level, enabling tools like Webpack and Rollup to perform tree-shaking (removing dead code) [3]. Asynchronous loading — the browser or Node.js fetches and parses modules without blocking [1]. Live bindings — imported values are live references, not copies, so changes in the exporting module are reflected in the importer [8]. Top-level await — ESM files can use await outside async functions, a feature CJS does not support [2]. Explicit file extensions — relative imports must include the extension (e.g., ./utils.js) [4]. Dynamic import() — Lazy Loading on Demand The import() operator (dynamic import) is a function-like expression that loads an ES module asynchronously and returns a Promise [7]. It works in both ESM and CJS contexts.\n// Load a module only when the user clicks a button button.addEventListener(\u0026#39;click\u0026#39;, async () =\u0026gt; { const { Chart } = await import(\u0026#39;./chart.js\u0026#39;); new Chart(data).render(); }); Common Use Cases Code splitting — ship only the JavaScript a user currently needs Conditional loading — load a polyfill only in older browsers Runtime path construction — build module paths from variables CJS ↔ ESM bridge — CommonJS code can import an ESM package using await import() [8] Dynamic imports are fully supported in all modern browsers and Node.js [7].\nFile Extensions: .js, .mjs, and .cjs The extension you choose tells Node.js (and your bundler) which module system to use [4][9].\nExtension Module System When to Use .js Determined by package.json \u0026quot;type\u0026quot; field Default; follows project-wide setting .mjs Always ES Module Force a single file to be ESM, regardless of package.json .cjs Always CommonJS Force a single file to be CJS inside an ESM-first project The package.json \u0026quot;type\u0026quot; Field // All .js files treated as ES Modules { \u0026#34;type\u0026#34;: \u0026#34;module\u0026#34; } // All .js files treated as CommonJS (default) { \u0026#34;type\u0026#34;: \u0026#34;commonjs\u0026#34; } Setting \u0026quot;type\u0026quot;: \u0026quot;module\u0026quot; in package.json makes every .js file in that package an ES module [4]. You can override per-file using .mjs (force ESM) or .cjs (force CJS) extensions regardless of the \u0026quot;type\u0026quot; setting [9].\nCJS vs ESM — Full Comparison Feature CommonJS (CJS) ES Modules (ESM) Syntax require() / module.exports import / export Loading Synchronous Asynchronous Where it runs Node.js (natively) Browser + Node.js Tree-shaking ❌ Not possible ✅ Supported Dynamic imports require() anywhere import() expression Top-level await ❌ ✅ Live bindings ❌ (copy at load time) ✅ File extension .cjs or .js .mjs or .js Status Legacy (still widely used) Modern standard Interoperability: Mixing CJS and ESM Node.js lets both systems coexist with important rules [8]:\n✅ ESM can import CommonJS packages — Node.js wraps module.exports as the default export. ❌ CJS cannot require() an ESM file — this throws an ERR_REQUIRE_ESM error. ✅ CJS can load ESM using dynamic await import() as a workaround [8]. Which Module System Should You Use in 2026? New projects → Use ES Modules (\u0026quot;type\u0026quot;: \u0026quot;module\u0026quot; in package.json). ESM is the standard, enables tree-shaking, and works in both browsers and Node.js [1][2]. Existing Node.js codebases → CommonJS is still practical and well-supported; migrate incrementally. Library authors → Ship dual packages (both CJS and ESM builds) via the \u0026quot;exports\u0026quot; field in package.json for maximum compatibility [3]. Legacy browser support → Use a bundler (Vite, Webpack, Rollup) that converts ESM to the target format; AMD/UMD are no longer necessary [6]. The JavaScript module landscape has converged: ESM is the clear winner for new code, but understanding CJS (and even AMD/UMD) is essential for navigating the vast npm ecosystem built on those older foundations.\nSources CommonJS vs ES Modules in JavaScript — Syncfusion Blogs CommonJS vs. ES Modules — Better Stack Community CommonJS vs. ES Modules in Node.js — LogRocket Blog ECMAScript Modules — Node.js Official Documentation What the heck are CJS, AMD, UMD, and ESM in Javascript? — DEV Community Navigating the module maze: History of JavaScript module systems — Codilime import() — MDN Web Docs A Deep Dive Into CommonJS and ES Modules in Node.js — AppSignal Blog What are .mjs, .cjs, .mts, and .cts extensions? — Total TypeScript Understanding MJS and CJS — RGB Studios ","permalink":"https://cloudmato.com/posts/javascript-module-systems-require-import-mjs/","title":"JavaScript Module Systems: require, import, .mjs \u0026 More"},{"content":"Every day your phone buzzes with news alerts, chat messages, and ride updates — all while the apps responsible for them are closed. But how exactly does a message originating on some remote server find its way to your locked screen in milliseconds? And no, your phone is not constantly asking \u0026ldquo;got anything for me?\u0026rdquo; — the answer is far more elegant than that.\nThe Big Question: Does the Phone Poll for Notifications? The short answer is no. Modern mobile operating systems do not repeatedly poll (pull) a server to check for new messages [8]. Instead, both iOS and Android maintain a single, long-lived persistent connection to a centralised gateway run by Apple or Google. When a notification is ready, the gateway pushes it down that open channel to your device [8]. This fundamental push model means:\nNo app needs its own polling loop running in the background Network and battery usage are dramatically reduced Notifications arrive nearly instantly, regardless of what the device is doing The Three-Party Architecture Both platforms share the same high-level three-party model [9]:\nYour app\u0026rsquo;s backend server — detects an event (new message, order shipped, etc.) and prepares a notification payload The platform push gateway — Apple Push Notification service (APNs) for iPhone, Firebase Cloud Messaging (FCM) for Android The user\u0026rsquo;s device — receives the notification through the OS and displays it At registration time, the app asks the OS for a unique device token — a cryptographically signed identifier that tells the gateway exactly which device (and which app on that device) should receive a given notification [1][4]. The app then sends this token to its own backend, which stores it for later use.\nHow Apple\u0026rsquo;s APNs Works (iPhone) Device Registration and the Token When an iOS app calls registerForRemoteNotifications(), the OS contacts Apple\u0026rsquo;s APNs servers and receives a device-specific push token [1]. This token encodes the device identity and the app identifier and is refreshed by iOS whenever it changes (e.g., after a restore). The token is then forwarded to the developer\u0026rsquo;s server, which uses it to address future notifications.\nThe Persistent TLS Connection The iPhone maintains a single, always-on, encrypted TCP connection to Apple\u0026rsquo;s APNs servers [2][3]. Key technical details:\nPrimary port: TCP 5223 — a dedicated APNs port [3] Fallback port: TCP 443 (HTTPS) — used on restricted Wi-Fi networks [3] Encryption: TLS (Transport Layer Security) throughout Keepalive packets: The device regularly sends lightweight heartbeat packets to prevent the server from closing the idle connection [3] Because this connection is maintained at the OS level — not by any individual app — it persists even when every app on your phone is suspended or closed. All apps on the device share the benefit of this single pipe.\nDelivering the Notification When your app\u0026rsquo;s backend has something to send, it authenticates with APNs (using a JWT or certificate) and POSTs a JSON payload containing the notification title, body, badge count, and any custom data [1]. APNs then routes this payload down the persistent connection to the target device. iOS receives it, wakes the relevant app briefly (if needed), and displays the notification.\niOS Intelligence: Smart Notification Ranking iOS 26 introduced Apple Intelligence-powered priority ranking that automatically promotes relevant notifications (a delivery arriving, a meeting starting, a direct reply needed) and demotes generic ones — surfacing the most important alerts at the top of the stack without any developer action needed [7].\nHow Android\u0026rsquo;s FCM Works Device Registration Similarly, on Android, when an app first calls FirebaseMessaging.getInstance().getToken(), the device registers with Google\u0026rsquo;s FCM infrastructure and receives a registration token [4]. This token is forwarded to the app\u0026rsquo;s backend and stored for later targeting.\nA Shared Persistent Connection FCM\u0026rsquo;s most important design principle is its single shared persistent connection maintained by the Google Play Services process on the device [5]. Every app that uses FCM — which is virtually every app on Android — routes its push notifications through this one connection. The benefits are substantial:\nBattery savings: No need for each app to maintain its own background socket [5] Simplicity for developers: Apps don\u0026rsquo;t need to manage connection lifecycle at all Reliability: Google\u0026rsquo;s infrastructure handles reconnects, retries, and delivery receipts Message Priority and Doze Mode Android\u0026rsquo;s Doze mode aggressively restricts background activity when the device is idle and unplugged to save battery [6]. FCM handles this with two distinct priority levels [5][6]:\nPriority Behaviour in Doze Typical Use Case Normal Batched and delivered at the next Doze maintenance window Newsletter, social feed update High Bypasses Doze; can wake a sleeping device immediately [6] Incoming call, chat message, alarm Developers set priority in the FCM payload. High-priority messages are strictly for user-visible, time-sensitive alerts — abusing this priority can result in Google throttling an app\u0026rsquo;s ability to wake the device [5].\nAndroid 16: AI-Powered Notification Organizer Released in mid-2025, Android 16 introduced a Notification Organizer that uses on-device AI to automatically categorise incoming notifications and silence lower-priority ones (such as promotions) without user intervention [7]. This mirrors Apple Intelligence\u0026rsquo;s approach on iOS and represents a convergence of both platforms toward smarter, less overwhelming notification experiences.\niOS vs Android: Side-by-Side Comparison Feature iOS (APNs) Android (FCM) Gateway service Apple Push Notification service Firebase Cloud Messaging Connection protocol Proprietary binary over TLS/TCP HTTP/2 or XMPP over TLS Primary port 5223 (fallback: 443) [3] 443 (HTTPS) Connection owner iOS kernel / system daemon Google Play Services process Permission required Explicit opt-in dialog required [7] Granted by default (Android 13+ requires opt-in) Global opt-in rate ~56% [7] ~67% [7] Priority levels Normal, Time-Sensitive, Critical Normal, High AI notification ranking Apple Intelligence (iOS 26+) Notification Organizer (Android 16+) Doze/Low-power bypass Silent push + background fetch APIs High-priority FCM flag [6] Why Not Just Poll? The Case for Push Before centralised push gateways existed, apps had to implement their own polling loops — waking up every few minutes to ask a server \u0026ldquo;anything new?\u0026rdquo; The costs were steep [8]:\nBattery drain: Each polling cycle wakes the radio, which is expensive Network overhead: Thousands of apps each making independent HTTP requests Latency: A notification could arrive up to the polling interval late Scalability: Backend servers fielding constant polling requests at scale The persistent-connection push model eliminates all of these. One open socket per device handles millions of potential notifications. The radio only wakes when there is actually something to deliver. And the gateway handles all the complexity of queuing, retrying failed deliveries, and expiring stale notifications [9].\nThe Takeaway Your phone is never blindly polling the internet for alerts. Both iPhone and Android take a sophisticated, battery-conscious approach: a single OS-managed persistent connection to Apple or Google\u0026rsquo;s global gateway sits quietly in the background. When a server anywhere in the world wants to reach your device, it posts a message to that gateway, which instantly pushes it down the open channel to your screen. The result is near-real-time delivery with minimal impact on battery life — a genuine engineering feat hiding behind every buzz and banner.\nSources Registering Your App with APNs — Apple Developer Documentation Apple Push Notification Service: How APNs Works in MDM — Fleet Does iOS maintain a constant connection? — Apple Developer Forums Firebase Cloud Messaging — Official Firebase Documentation Ensure your FCM Notifications Reach Users on Android — Firebase Blog, April 2025 Optimize for Doze and App Standby — Android Developers Push Notifications on iOS vs Android: How They Work in 2026 — MobiLoud Pull vs Push Architecture for Mobile — Microsoft Mobile Engineering, Medium Push Notifications Deep Dive: The Ultimate Technical Guide to APNs \u0026amp; FCM — Spritle Scaling Push Notifications to 50 Million Devices — Design Gurus Substack ","permalink":"https://cloudmato.com/posts/how-mobile-push-notifications-work-ios-android/","title":"How Mobile Push Notifications Work: iOS \u0026 Android"},{"content":"India\u0026rsquo;s most trusted examination board, CBSE, was rocked by a sweeping cybersecurity scandal in May 2026 when a 19-year-old ethical hacker demonstrated live, unrestricted access to its On-Screen Marking (OSM) evaluation system — including shell access to production servers and free downloads of student answer sheets from an unsecured cloud bucket [1][2]. The breach put the personal and academic data of an estimated 20 lakh (2 million) Class 12 students at risk [5]. This article unpacks every layer of the hack, explains what it means for students and families, and maps out what the government must do to prevent a repeat.\nThe 2026 CBSE OSM Breach: What Happened On 22 May 2026, Nisarga Adhikary — a 19-year-old ethical hacker and Class 12 student — went public on social media with a detailed breakdown of critical security flaws inside CBSE\u0026rsquo;s OnMark portal, the third-party system used by examiners to evaluate Class 12 answer sheets digitally [2][3]. What made his disclosures explosive was not just the vulnerabilities themselves, but the timeline: CBSE officials had previously denied that any such flaws existed, yet Adhikary demonstrated full create, read, update, and delete (CRUD) access along with shell access to CBSE\u0026rsquo;s live production servers [1]. He also obtained super-admin privileges on another OnMark subdomain responsible for evaluation across multiple universities [1].\nThe portal is operated by a private vendor, COEMPT Eduteck, under contract with CBSE. Adhikary described the system\u0026rsquo;s security posture as \u0026ldquo;insanely insecure\u0026rdquo; [4] — a characterization that even CBSE\u0026rsquo;s own subsequent audit could not convincingly refute.\nVulnerabilities Laid Bare Adhikary identified at least six high-severity vulnerabilities across CBSE-linked systems [3]. The table below summarises the critical flaws uncovered:\nVulnerability Description Risk Level Hardcoded Master Password A universal secret password embedded in publicly readable code, bypassing per-user authentication Critical OTP Exposed in Browser One-time passwords sent to examiners visible in browser HTTP responses, making bypass trivial High Password Reset Without Verification Any examiner\u0026rsquo;s account password could be reset with no identity check High AWS S3 Bucket — No Auth Required Answer sheets and question papers downloadable by anyone with the bucket URL Critical MD5 Password Hashing on SARAS Portal Outdated, easily crackable hash algorithm used on CBSE\u0026rsquo;s school affiliation system High Admin Password \u0026ldquo;123456\u0026rdquo; An administrative portal linked to CBSE\u0026rsquo;s ecosystem reportedly used this trivial password Critical Sources: [3][4][8][14]\nThe Amazon Web Services (AWS) S3 storage bucket linked to the OnMark system allegedly allowed anyone to browse and download scanned answer booklets and question papers from 2026 board examinations without providing any credentials [4][15]. This is a classic insecure cloud configuration — one of the most common and most preventable causes of mass data exposure in modern IT.\nImpact on Students: Who Gets Hurt? The real-world consequences of these vulnerabilities fall squarely on millions of students:\nPrivacy violation: Scanned answer booklets — containing handwriting, roll numbers, and school details — were allegedly accessible to anyone with a browser and a URL [15]. Risk of marks manipulation: The ability to impersonate examiners and gain super-admin access theoretically allowed alteration of marks, though CBSE states no such tampering has been confirmed [1][14]. Financial fraud: A separate malicious attack on the re-evaluation portal caused fee display amounts to fluctuate wildly between ₹1 and ₹68,000, overcharging approximately 50 students [7]. Examination integrity compromised: With 2026 question papers allegedly accessible in the AWS bucket, the fairness of the board exam itself came under a cloud [4]. Political uproar: Congress leader Jairam Ramesh called it \u0026ldquo;a massive data leak that has put the privacy of 20 lakh students at risk,\u0026rdquo; demanding government accountability [5][6]. The COEMPT Eduteck Controversy At the centre of the storm is COEMPT Eduteck, the private IT vendor operating CBSE\u0026rsquo;s OSM platform [17]. Critics allege that successive Requests for Proposal (RFPs) were modified across multiple tender rounds in ways that may have altered eligibility criteria to the vendor\u0026rsquo;s benefit [17]. Key red flags include:\nThe requirement for robotic scanning machines — a critical physical security safeguard — was reportedly removed in the third RFP [6]. Scanned images showed signs of being captured via mobile phone cameras rather than certified secure scanners, raising physical data-handling concerns [6]. Congress formally demanded a probe into the tendering process and the vendor\u0026rsquo;s conduct [17]. CBSE has maintained that identified vulnerabilities \u0026ldquo;have been contained,\u0026rdquo; attributing some hacking demonstrations to a testing environment rather than live production systems — a claim disputed by cybersecurity researchers [3][10].\nFake DigiLocker: A Shadow Threat Compounding the crisis, the Ministry of Electronics and Information Technology (MeitY) issued a public warning about a counterfeit DigiLocker website operating in the shadows of the CBSE controversy [9]. The fraudulent portal:\nMimics the official government DigiLocker interface in look and feel. Claims to provide DigiLocker and CISCE services to students. Is engineered to harvest student credentials and personal information. Students and parents rushing to verify board results are prime phishing targets. MeitY urges users to access DigiLocker only via digilocker.gov.in and to report suspect sites through CERT-In\u0026rsquo;s official channels [9].\nGovernment Response So Far The government\u0026rsquo;s immediate response has been energetic, though its long-term follow-through remains to be seen:\nIIT Task Force Deployed: CBSE brought in cybersecurity experts from IIT Madras and IIT Kanpur, alongside government agency professionals, to audit OnMark and shore up remaining vulnerabilities [11]. Vulnerability Containment Declared: CBSE stated all \u0026ldquo;identifiable vulnerabilities\u0026rdquo; have been contained, with additional sweeps still ongoing [10]. DigiLocker Pivot Announced: The Ministry of Education confirmed that from the next academic year, CBSE Class 12 answer sheets will move to DigiLocker, reducing dependence on third-party vendor portals entirely [16]. National Cybersecurity Strategy 2026 Launched: This umbrella policy framework mandates coordination among CERT-In, state police cyber cells, and private sector stakeholders for faster detection and response across critical sectors [13]. CERT-In Scale-Up: In 2024–25, CERT-In handled over 29.44 lakh cyber incidents, issued 1,530 alerts, 390 vulnerability advisories, and conducted over 9,700 security audits across government and critical infrastructure [12]. What More Must Be Done: A Policy Roadmap Reactive patching is not a strategy. Here is what systemic reform must look like:\nMandatory Vendor Security Standards Any EdTech vendor handling government education data must pass a CERT-In-approved third-party security audit before contracts are signed and renewed annually. Security requirements in government RFPs must be non-negotiable, version-locked clauses — not items that can be quietly diluted across tender revisions. Formal bug bounty programmes should be established for all government education portals, giving ethical hackers like Adhikary a safe, legally protected, and financially rewarded disclosure channel. Cloud Configuration Governance All government-linked cloud storage (AWS S3, Azure Blob, GCP) must undergo automated misconfiguration scanning — a standard already mandated in the EU, USA, and Australia. The Digital Personal Data Protection (DPDP) Act 2023 must be enforced with meaningful penalties: vendors who expose student data through negligence should face financial liability, not just advisory notices. A Dedicated Edu-CERT India should establish a sectoral Education CERT (Edu-CERT) modelled on the financial sector\u0026rsquo;s CERT-Fin, with authority to independently audit, respond to, and penalise vendors operating education infrastructure in real time — without waiting for student complaints to reach newspapers. Cybersecurity Talent Pipeline The National Cybersecurity Strategy 2026 targets training 5 lakh cybersecurity professionals over five years [13]; a dedicated portion must be channelled specifically into edtech security and government portal auditing. Cybersecurity must be added to school and college curricula so the next generation of administrators and developers builds security-first thinking from the ground up. Student and Parent Awareness Annual digital literacy campaigns should educate students, parents, and school administrators on verifying official URLs, identifying phishing portals, and reporting data anomalies through official channels [9]. CBSE should publish a clear public incident-response protocol so students know what to do the moment a breach is suspected. The CBSE crisis is ultimately a symptom of a systemic gap: India\u0026rsquo;s rapid digital expansion in public services has not consistently been matched by equivalent investment in security architecture. As examination results, identities, and academic records become ever-more tightly bound to digital systems, the stakes only grow higher — and the window for meaningful structural reform is right now, before the next breach makes headlines.\nSources CBSE Online Marking Portal Hacked After Officials Denied Security Flaws — MediaNama How a 19-year-old student hacked CBSE\u0026rsquo;s OSM portal, exposed vulnerabilities — The Print CBSE admits vulnerabilities in OnMark portal after teen hacker alleges answer sheet leak — Careers360 \u0026lsquo;Insanely insecure\u0026rsquo;: Ethical hacker alleges CBSE answer sheets were publicly accessible — BusinessToday CBSE Class 12 data leak: 20 lakh students\u0026rsquo; privacy at risk, says Cong — Asianet Newsable Massive data leak that has put privacy of 20 lakh students at risk: Congress slams government — ANI News CBSE Portal Faces Malicious Attack Affecting Approximately 50 Students — The Chenab Times \u0026lsquo;Password was 123456\u0026rsquo;: Student alleges fresh security lapses in CBSE-linked systems — BusinessToday Govt warns students about fake DigiLocker website amid CBSE OSM row — Digit CBSE says OSM portal \u0026lsquo;vulnerabilities contained\u0026rsquo;, deploys IIT teams for cybersecurity review — India TV News CBSE Deploys IIT Madras and IIT Kanpur Experts After Security Concerns Over OnMark Portal — Swarajya Mag Government Strengthens Cybersecurity Across Critical Sectors; Over 9,700 CERT-In Audits Conducted in 2024–25 — PIB India Launches National Cybersecurity Strategy 2026 — Education Post CBSE OSM Controversy Explained: Board Admits Security Gaps After Portal Was Hacked — Gulf News CBSE Answer Sheet Leak Row: Students Allege AWS Data Breach, Poor Scanning and Evaluation Errors — OneIndia From next year, CBSE Class 12 answer sheets to be available on DigiLocker — Careers360 Congress raises alarm over alleged CBSE answer sheet leak, questions evaluation contractor — The Statesman ","permalink":"https://cloudmato.com/posts/cbse-portal-hacks-vulnerabilities-2026/","title":"CBSE Portal Hacks 2026: Risks, Impact \u0026 Government Fixes"},{"content":"Cloud computing is reshaping how the world runs software — the global market is on track to approach $1 trillion in 2026 [1], powered by AI workloads, SaaS expansion, and enterprise digital transformation. Yet if you already deploy your apps on a DigitalOcean Droplet or a similar VPS, you might be asking: do I actually need to change anything? Here is an honest, practical answer.\nWhat Is Cloud Computing? Cloud computing is the delivery of computing resources — servers, storage, databases, networking, software, and analytics — over the internet on a pay-as-you-go basis [1]. Rather than owning physical hardware, you rent capacity from a provider and access it from anywhere. The three dominant players are AWS (~33% market share), Microsoft Azure (~23%), and Google Cloud (~12%), collectively controlling more than 60% of the global cloud infrastructure market [3].\nThe term \u0026ldquo;the cloud\u0026rdquo; is essentially shorthand for someone else\u0026rsquo;s computers, managed at massive scale, made available to you on demand [2].\nThe Three Cloud Service Models Not all cloud services are the same. Providers structure their offerings in three distinct layers [4][5]:\nIaaS — Infrastructure as a Service IaaS hands you virtual machines, networking, and storage while the provider owns and maintains the physical hardware. You still install and manage your OS, runtime, and application. AWS EC2, Azure Virtual Machines, and Google Compute Engine are the canonical examples [5].\nFull control over the software stack You are responsible for OS patching and security hardening Best for teams with DevOps expertise who need flexibility PaaS — Platform as a Service PaaS adds a fully managed runtime on top of IaaS. You push code; the platform handles deployments, OS updates, and scaling. Heroku, Google App Engine, and Vercel are popular PaaS options [4].\nDevelopers skip server configuration entirely Faster from git push to production Reduced operational overhead — ideal for small teams SaaS — Software as a Service SaaS is the finished product: ready-to-use software in a browser, fully managed by the vendor. Gmail, Salesforce, Dropbox, and Slack are everyday examples [5]. There is nothing to install, patch, or scale — the provider does it all.\nKey Benefits of Cloud Computing Cloud adoption has exploded because it solves problems that a traditional VPS cannot address at scale [1][2]:\nElastic Scalability Cloud platforms provision or release CPU, RAM, and storage in seconds — no reboot required. Auto-scaling groups detect a traffic surge and spin up new instances automatically, then remove them when demand drops [7]. Handling an unexpected viral moment or a product launch no longer requires guesswork about server sizing. True Pay-As-You-Go Pricing You are billed for compute seconds, GBs stored, and API calls consumed — never for idle capacity [8]. Startups can launch with near-zero infrastructure spend and let costs grow only with revenue. Serverless functions (AWS Lambda, Google Cloud Functions) take this further: if nobody calls your endpoint, your bill is literally $0. Built-in Global Distribution AWS operates 34+ geographic regions; Azure and Google Cloud cover similar footprints [2]. Deploying your app closer to users in Tokyo, Frankfurt, or São Paulo is a configuration change, not a hardware procurement order. Content Delivery Networks (CDNs) and edge caches are first-class, managed services. High Availability and Disaster Recovery Cloud workloads run across multiple Availability Zones — physically separate data centres within a region. If one zone fails, traffic reroutes automatically with no manual intervention [7][8]. Industry SLAs promise 99.99% uptime — fewer than 53 minutes of downtime per year. Enterprise-Grade Security Without the Price Tag Hyperscalers invest billions in DDoS mitigation, hardware-level encryption, IAM, and compliance certifications (SOC 2, ISO 27001, HIPAA, GDPR) [1][11]. A 3-person startup gets the same physical security posture as a Fortune 500 company. Managed Services That Eliminate Toil Need a relational database? Spin up AWS RDS or Cloud SQL — no DBA needed. Need a message queue? Use SQS or Pub/Sub — no RabbitMQ cluster to babysit. Machine learning APIs, video transcoding, real-time analytics: all available as managed, pay-per-use services [12]. The Case for VPS: Where DigitalOcean Still Wins Despite everything listed above, a plain VPS is far from obsolete. DigitalOcean, Linode (Akamai Cloud), and Hetzner occupy a very real, practical niche [6][9].\nA VPS is a virtual machine on a single physical host. You get root access, a fixed slice of CPU and RAM, and a predictable monthly invoice — typically $6–$24 for an entry-level Droplet [6][10].\nVPS still makes excellent sense when:\nTraffic is predictable. A portfolio site, a small SaaS with steady visitors, or a CI runner doesn\u0026rsquo;t need auto-scaling. Paying for the ability to scale when you never will is pure overhead [9]. You need a fixed monthly budget. No surprise bills from a Lambda function stuck in a loop or accidental S3 egress charges. Full-stack control matters. Root SSH lets you install any runtime, tweak kernel parameters, and avoid cloud-specific abstractions or vendor lock-in [6]. You host multiple small projects cheaply. One $12/month Droplet can serve a dozen low-traffic apps using Nginx virtual hosts or Docker containers. Your users are all in one region. A nearby VPS can outperform a multi-region cloud setup for latency-sensitive workloads that don\u0026rsquo;t benefit from geographic distribution. Research consistently shows that businesses running consistent, predictable workloads often pay 40–60% more on cloud platforms than they would for equivalent VPS resources [8]. That cost delta is real and must be weighed honestly.\nCloud vs. VPS: Head-to-Head Comparison Feature Cloud (AWS / Azure / GCP) VPS (DigitalOcean / Linode) Scalability Auto-scales in seconds Manual upgrade required Uptime SLA 99.99% (multi-zone) ~99.9% (single server) Pricing model Pay-per-use (variable) Fixed monthly fee Global regions 30–40+ worldwide Fewer; varies by provider Managed services Databases, AI, queues, CDN Minimal; mostly DIY Security tooling Built-in IAM, DDoS, compliance certs Basic firewall; self-managed Learning curve High (hundreds of services) Low; straightforward Linux/SSH Best for Variable loads, microservices, AI Stable apps, dev environments, side projects So — Should You Ditch Your VPS? Not necessarily. Think of a VPS as a reliable, affordable apartment — you know the rent, you control every corner of it, and it suits you perfectly when your needs are stable. The cloud is more like a global hotel chain: more expensive per square foot, but you can check in with a different room size in any city, tonight, on zero notice [11].\nUpgrade to cloud computing if:\nTraffic to your application spikes unpredictably. You need multi-region redundancy or global CDN delivery. You want managed AI, ML, or data pipeline services without running infrastructure. Your team lacks bandwidth to patch and maintain servers. Compliance requirements demand SOC 2, HIPAA, or GDPR certifications [1][11]. Stick with your VPS if:\nTraffic is steady and well-understood. You want a transparent, fixed monthly bill. You are building side projects, dev environments, or low-traffic client sites. You need root-level control without cloud vendor lock-in. Your app never justifies the auto-scaling overhead [9][10]. Many teams ultimately adopt a hybrid approach — cloud hyperscalers for production and burst capacity, VPS for cost-sensitive, stable workloads. Multi-cloud and hybrid adoption hit 89% among enterprises in 2026 [3], proving the industry is not picking one or the other, but picking both strategically. The cloud is not the answer to every problem — but for growing applications that demand reliability, global reach, and developer velocity, it is increasingly hard to beat.\nSources Cloud Computing Guide 2026: Advantages, Disadvantages, and Real Business Impact Cloud Computing in 2026: Benefits, Business Impact, and Future Growth Cloud Market Share 2026: AWS vs Azure vs Google Revenue \u0026amp; Full Stats IaaS vs. PaaS vs. SaaS — Red Hat SaaS vs PaaS vs IaaS – Types of Cloud Computing – AWS What is a Virtual Private Server (VPS)? — Google Cloud Cloud vs. VPS Hosting: Pros, Cons and Key Differences — Cloudways VPS vs Cloud Hosting: Performance, Cost \u0026amp; Security Comparison for 2026 — HostMyCode VPS vs. Cloud Hosting: Key Differences, Pros \u0026amp; Cons — SiteGround Cloud Server vs VPS Hosting: Price Comparison 2026 — LetsCloud Cloud Hosting vs VPS vs Dedicated: Which Is Best for 2026? — SkyNet Hosting Cloud Computing: Types, Benefits \u0026amp; Use Cases Explained (2026) — Techimply ","permalink":"https://cloudmato.com/posts/cloud-computing-vs-vps-hosting/","title":"Cloud Computing vs VPS: Benefits \u0026 Key Differences"},{"content":"India is the world\u0026rsquo;s second-largest smartphone market — and one of its most targeted by cybercriminals. In 2025 alone, Indians filed 28.15 lakh cybercrime complaints and lost a staggering ₹22,495 crore to digital fraud [2]. If you use a smartphone for banking, UPI payments, or even just WhatsApp, you are a target. Here is your complete, no-nonsense guide to locking down your phone before the next scam call reaches you.\nThe Scale of the Problem The numbers paint a grim picture. Cybercrime cases in India jumped 24% in 2025 compared to the previous year [2], and the Indian government has had to block over 7.81 lakh SIM cards and 2,08,469 phone IMEIs linked to fraudulent activity [3]. UPI fraud alone accounted for more than ₹805 crore in just the first eight months of FY26 — and that only counts what was actually reported, since an estimated 51% of UPI fraud victims never file a complaint [1].\nThe fraud is not random. Scammers run organised operations, use AI voice-cloning, fake government portals, and convincing deepfake videos to squeeze money out of ordinary people every single day.\nThe Most Common Phone Scams Targeting Indians Digital Arrest \u0026amp; Impersonation Scams \u0026ldquo;Digital arrest\u0026rdquo; is the fastest-growing psychological fraud in India [1]. A caller — often via WhatsApp video — poses as a CBI officer, customs official, or TRAI representative. They claim your phone number is linked to money laundering or drug trafficking and put you on a fake \u0026ldquo;digital arrest,\u0026rdquo; demanding you stay on the call until you pay a fine or share bank credentials [5].\nKey red flags:\nThe caller demands secrecy and urges you to stay connected for hours They ask you to transfer money to a \u0026ldquo;safe government account\u0026rdquo; They use official-looking screen backgrounds and fake ID cards They threaten immediate arrest if you hang up Reality check: No government agency in India — not the CBI, ED, TRAI, or police — arrests citizens over a phone or video call [5].\nUPI, OTP \u0026amp; Payment Fraud Fraudsters send fake payment-request QR codes, counterfeit PhonePe/GPay screens, and phishing links disguised as bank SMS messages [7]. Once you scan or click, your OTP is captured and your account is drained within minutes.\nCommon UPI attack vectors:\nFake collect requests — you receive a ₹1 \u0026ldquo;test\u0026rdquo; payment request; accepting it triggers a larger debit Screenshare scams — a fake \u0026ldquo;bank support\u0026rdquo; agent asks you to install AnyDesk or TeamViewer; they watch your OTP in real time [8] Impersonation apps — lookalike PhonePe/GPay APKs downloaded outside the Play Store that harvest your credentials [7] eSIM Hijacking \u0026amp; SIM Swap Fraud As India adopts eSIMs, a new attack has emerged. Fraudsters call your telecom operator pretending to be you, convince them to issue an eSIM, then intercept every OTP that arrives on your number [6]. Within minutes they control your bank account, email, and UPI. South Indian Bank warns that eSIM scams are rising sharply in 2025–26 and that the whole heist can complete in under ten minutes [6].\nHow to Fortify Your Phone Against Scammers Lock Down Your SIM Card Your mobile number is the master key to your entire digital life. Treat it accordingly.\nSet a SIM PIN (Settings → SIM card → SIM PIN on Android; Settings → Cellular → SIM PIN on iPhone). Anyone who steals your phone cannot make calls or receive OTPs without this PIN [4]. Enable SIM lock with your carrier — call Airtel, Jio, or Vi and ask for a port-block or an eSIM activation freeze so no one can port or duplicate your number without visiting a store in person. Check your registered SIMs at sancharsaathi.gov.in — India\u0026rsquo;s official Sanchar Saathi portal lets you see all SIM cards registered under your Aadhaar and instantly block any you don\u0026rsquo;t recognise [3]. Secure Your UPI and Banking Apps Threat Wrong behaviour Safe behaviour Fake collect request Approve thinking it\u0026rsquo;s a refund Reject all incoming requests you did not initiate Screen-share demand Install AnyDesk for \u0026ldquo;bank support\u0026rdquo; End the call; banks never ask for remote access QR code payment Scan merchant QR without checking name Verify merchant name matches before paying Unknown APK Download PhonePe from a link Download only from Google Play / Apple App Store OTP request Share with \u0026ldquo;bank executive\u0026rdquo; OTPs are yours alone — never share with anyone Additional steps to take right now:\nEnable biometric lock (fingerprint/face) inside every UPI and banking app [8]. Set a daily UPI transaction limit in your bank\u0026rsquo;s app — most allow limits as low as ₹5,000. Turn on SMS/email alerts for every transaction, no matter how small. Remove saved cards from websites you no longer use regularly. Harden Your Device Settings Keep your OS and apps updated. Security patches close the exact vulnerabilities scammers exploit [4]. Never connect to public Wi-Fi for any banking or payment task. Use mobile data or a trusted VPN instead [4]. Audit app permissions monthly. Revoke microphone, camera, and contacts access from any app that has no genuine need for them. Enable Google Play Protect (Android) or keep Lockdown Mode available (iPhone) for high-risk situations. Use strong, unique passwords and a reputable password manager. Enable two-factor authentication (2FA) on all email and social accounts [4]. Do not side-load APKs. Disable \u0026ldquo;Install unknown apps\u0026rdquo; permission on Android — most banking trojans arrive this way [7]. What to Do If You Have Already Been Scammed Speed is everything. Every minute you wait gives fraudsters more time to move the money.\nCall 1930 immediately — India\u0026rsquo;s 24×7 National Cyber Crime Helpline can freeze the recipient account before funds are transferred [1]. File a complaint at cybercrime.gov.in with all transaction IDs, screenshots, and the fraudster\u0026rsquo;s number. Call your bank\u0026rsquo;s fraud line and request an immediate transaction dispute. Report the phone number/WhatsApp account on Sanchar Saathi\u0026rsquo;s Chakshu portal to get it blocked nationally [3]. Do not transfer more money to \u0026ldquo;recover\u0026rdquo; what was lost — a second scammer often appears offering to help. Government Tools Every Indian Should Bookmark India has built several free resources to fight back against phone fraud:\nResource Purpose Link Helpline 1930 Report cyber fraud, freeze accounts Call 1930 Cybercrime Portal File formal FIR-level complaint cybercrime.gov.in Sanchar Saathi Manage/block your SIM cards sancharsaathi.gov.in Chakshu (Sanchar Saathi) Report spam calls, SMS, WhatsApp fraud sancharsaathi.gov.in/sfc Cyber Fraud Risk Indicator RBI + Govt real-time transaction monitoring Automatic (no action needed) The government\u0026rsquo;s I4C initiative has already blocked fraudulent transactions worth over ₹8,031 crore since launch [3]. These systems work — but only if you report quickly.\nThe Golden Rule Scammers rely on urgency and fear. The moment a call creates panic — \u0026ldquo;your number will be blocked,\u0026rdquo; \u0026ldquo;you are under digital arrest,\u0026rdquo; \u0026ldquo;your bank account is compromised\u0026rdquo; — that is your cue to hang up, breathe, and verify independently by calling the official number of the agency being impersonated. No legitimate institution will punish you for double-checking.\nSecuring your phone is not a one-time task; it is a weekly habit. Set a monthly reminder to review app permissions, update passwords, and check sancharsaathi.gov.in for unfamiliar SIM registrations. The scammers are persistent — your vigilance must be too.\nSources Digital Fraud in India 2026: Rising Threats, Common Scams \u0026amp; How to Protect Yourself Cybercrime in India 2025: 24% Spike, ₹22,495 Crore Lost Curbing Cyber Frauds in Digital India — Press Information Bureau 10 Common Mobile Phone Scams in India and How to Avoid Them Protect Yourself from Digital Arrest Scams: A Must-Read Guide The Rise of eSIM Fraud in India and What You Can Do About It UPI Scam 2026: How Hackers Steal Money Using OTP and Fake Apps UPI Frauds: How to Secure Your Account — The Complete Guide ","permalink":"https://cloudmato.com/posts/how-to-secure-your-phone-from-scams-india/","title":"How to Secure Your Phone From Scams in India (2026)"},{"content":"Picking the right AI tool in 2026 is less about which is smartest and more about which fits your wallet. The market now spans from genuinely powerful free tiers all the way to $300-per-month power-user subscriptions — and the gap between them is wider than ever. Here\u0026rsquo;s a definitive, budget-first guide to every major AI tool ranked from $0 to $300/month, so you can stop guessing and start choosing.\nThe AI Pricing Landscape in 2026 One defining feature of the 2026 AI market is pricing convergence at the $20/month mark [1]. ChatGPT Plus, Claude Pro, Google Gemini AI Pro, Microsoft Copilot Pro, and Perplexity Pro have all independently landed at approximately $20/month for their standard paid tier [2]. Above and below that anchor, prices fan out dramatically — from completely free tiers to enterprise plans costing hundreds per month [3].\nThe three broad categories to understand before spending a cent:\nGeneral-purpose chatbots (ChatGPT, Claude, Gemini, Copilot, Grok) — best for writing, research, coding, and everyday Q\u0026amp;A Specialized writing tools (Jasper, Grammarly, Writesonic, Wordtune) — best for content creators and marketers Image/media generators (Midjourney) — best for visual content creation Tier 1: Free AI Tools (Best for Casual Users) Every major AI platform now offers a meaningful free tier — not just a demo, but genuinely usable access [4].\nTool Free Tier Model Key Limits ChatGPT GPT-5.3 10 messages per 5 hours [5] Claude Claude 3.x Daily message cap, no Projects Gemini Gemini 2.x Limited prompts, no Advanced features Copilot GPT-4o based Unlimited basic chat, limited daily boosts Perplexity GPT-4o / Claude Limited Pro searches per day Grok Grok 3 Via X.com, limited queries Meta AI Llama-based Free, built into WhatsApp/Instagram Best free pick: Perplexity for research (cites sources) and Claude for long-form writing. Both punch well above their $0 price tag [4].\nTier 2: Budget Plans — $7 to $19/month For users who need more volume without committing to $20/month, several solid options exist.\nWriting \u0026amp; Grammar Tools Wordtune — $6.99/month; rewrites and paraphrases sentences with strong tonal control [7] Grammarly Premium — $12/month; writing quality checks, tone adjustments, and 1,000 AI prompts per month [7] Writesonic — from $12.67/month for 200K words; $16/month for unlimited words, strong SEO features [7] Chatbot Upgrades ChatGPT Go — $8/month; more message volume than free, but lacks GPT-5.5 and Sora [5] SuperGrok Lite — $10/month; launched March 2026, entry-level access to Grok with DeepSearch [8] Midjourney Basic — $10/month; 200 fast GPU image generations per month [6] Best budget pick: Grammarly Premium at $12/month for writers; Writesonic Unlimited at $16/month for content marketers needing high-volume output.\nTier 3: Standard Plans — $20 to $40/month (The Sweet Spot) This is where most users should start. Five of the biggest AI platforms cluster tightly at $20/month, giving you strong feature parity for the same price [2].\nTool Price Best For Standout Feature ChatGPT Plus $20/mo All-round use GPT-5.5, Sora, Codex, Deep Research (10 runs) [5] Claude Pro $20/mo Writing \u0026amp; coding Longest context window, superior prose quality [3] Gemini AI Pro $19.99/mo Google Workspace users Integrates with Gmail, Docs, Drive [4] Copilot Pro $20/mo Microsoft 365 users Embedded in Word, Excel, Outlook, Teams [2] Perplexity Pro $20/mo Research \u0026amp; fact-checking Real-time web citations, multi-model selection [9] Grok SuperGrok $30/mo X users, real-time news DeepSearch, Big Brain Mode, 128K context [8] Midjourney Standard $30/mo Image generation 15 fast GPU hrs + unlimited Relax mode images [6] Choosing Within the $20 Tier Go with ChatGPT Plus if you want the broadest feature set and access to OpenAI\u0026rsquo;s tools ecosystem [5] Go with Claude Pro if writing quality, nuance, and long documents are your priority [3] Go with Gemini AI Pro if you\u0026rsquo;re already inside Google Workspace — the integration alone justifies the cost [4] Go with Copilot Pro if your workflow runs on Microsoft 365 [2] Go with Perplexity Pro if you do heavy research and hate hallucinated facts [9] Tier 4: Pro Plans — $49 to $125/month Power users and professionals who have maxed out standard plans move here [3].\nSpecialized Writing Platforms Jasper Creator — $49/month; 50,000 words/month, brand voice, one user seat [10] Jasper Teams — $125/month; unlimited words, three user seats, collaboration features [10] AI Chatbot Power Tiers ChatGPT Pro ($100/month) — Launched April 9, 2026; 5× Plus usage limits, GPT-5.5 Pro, full Codex access, ideal for developers [5] Claude Max ($100/month) — Anthropic\u0026rsquo;s mid-tier Max plan; 5× Claude Pro usage with priority queuing [3] Google Gemini AI Ultra ($100/month) — Repriced after Google I/O 2026; includes Gemini 3.1 Ultra, Google One 2TB storage, and YouTube Premium [4] Image Generation Midjourney Pro — $60/month; 30 fast GPU hrs, stealth mode, 12 concurrent jobs [6] Best pro pick: ChatGPT Pro at $100/month for developers and power users, or Claude Max if writing quality matters most [3][5].\nTier 5: Ultra \u0026amp; Enterprise Plans — $120 to $300/month These plans are for professionals with demanding workloads, teams, or niche requirements [8].\nMidjourney Mega — $120/month; 60 fast GPU hours, maximum concurrent generations, priority queuing [6] ChatGPT Pro (full) — $200/month; truly unlimited access to all models, unlimited Sora video, research previews [5] Claude Max ($200 tier) — Higher usage ceiling than the $100 tier; for teams doing large-scale content or code generation [3] Google Gemini AI Ultra — $249.99/month (original pricing, discounted to $124.99 for first 3 months); highest Gemini rate limits [4] Grok SuperGrok Heavy — $300/month; the most expensive mainstream consumer AI plan ever released; grants Grok 4 Heavy access, 256K context window, and maximum rate limits across all features [8] Honest advice: Unless you are a professional making money because of AI output, the $200–$300/month tier rarely justifies itself. The $100/month Pro tiers from OpenAI or Anthropic deliver 80–90% of the capability at half the price [2].\nQuick-Reference Pricing Table: All Tools Ranked by Price Price Tool Plan Name $0 ChatGPT, Claude, Gemini, Copilot, Perplexity, Grok, Meta AI Free $6.99 Wordtune Starter $8 ChatGPT Go $10 SuperGrok, Midjourney Lite / Basic $12 Grammarly Premium $13–$16 Writesonic Individual / Unlimited $19.99 Google Gemini AI Pro $20 ChatGPT, Claude, Copilot, Perplexity Plus / Pro $30 Grok SuperGrok, Midjourney SuperGrok / Standard $40 Grok via X Premium+ X Premium+ $49 Jasper Creator $60 Midjourney Pro $100 ChatGPT, Claude, Gemini Pro / Max / Ultra $120 Midjourney Mega $125 Jasper Teams $200 ChatGPT, Claude Pro / Max $249.99 Google Gemini AI Ultra $300 Grok SuperGrok Heavy Which AI Tool Should You Choose? The \u0026ldquo;best\u0026rdquo; AI tool is the one that solves your specific problem at a price you won\u0026rsquo;t regret. Here\u0026rsquo;s the quick decision guide:\nBudget is $0: Start with Perplexity (research) or Claude (writing) Budget is ~$10–$16/month: ChatGPT Go for chat, Grammarly for writing quality Budget is ~$20/month: ChatGPT Plus for versatility; Claude Pro for writing; Gemini AI Pro for Google users Budget is ~$30/month: Add Midjourney Standard for images, or Grok SuperGrok if you need real-time X data Budget is ~$100/month: ChatGPT Pro or Claude Max for heavy professional workloads Budget is $200+/month: Only consider this if AI directly generates revenue for you [2][3] No single tool dominates every category in 2026 — the market has matured into specialization [4]. The wisest strategy is to start free, validate the use case, then step up exactly one pricing tier at a time.\nSources AI Pricing Compared: Every Plan From ChatGPT, Claude, Gemini, Copilot \u0026amp; More (2026) — AIViewer.ai AI Pricing Comparison 2026: ChatGPT vs Claude vs Gemini — AIonX AI Pricing Compared 2026: ChatGPT vs Claude vs Perplexity vs Gemini — FindSkill.ai Best AI Chatbot Services in 2026 Compared: Pricing, Features, and Performance — TechTimes ChatGPT Plans — Official OpenAI Pricing Page Midjourney Pricing 2026: Plans, Costs \u0026amp; Reddit Verdict — AI Tool Discovery AI Tools Pricing Comparison 2026: Every Plan, Ranked by Value — ComputerTech Grok Pricing 2026: SuperGrok, X Premium+, Heavy \u0026amp; API Costs — FelloAI ChatGPT Plans Compared: Free vs Plus vs Pro vs Business vs Enterprise (2026) — IntuitionLabs Jasper Pricing 2026 — G2 ","permalink":"https://cloudmato.com/posts/best-ai-tools-2026-price-comparison/","title":"Best AI Tools in 2026: Every Plan Ranked by Price"},{"content":"The iPhone vs Android debate is one of tech\u0026rsquo;s most enduring rivalries — and in 2026, it remains genuinely competitive. Yet despite Android\u0026rsquo;s dramatic improvements in update longevity and camera hardware, the iPhone continues to hold meaningful, measurable advantages across several critical dimensions. Whether you\u0026rsquo;re a first-time buyer or considering a switch, here are six areas where iPhone consistently comes out ahead.\nSecurity and Privacy: iPhone\u0026rsquo;s Most Durable Lead When it comes to protecting your data, iPhone holds a structural edge that goes beyond marketing. Apple\u0026rsquo;s App Tracking Transparency (ATT) framework, introduced in iOS 14.5 and refined since, blocks third-party ad trackers by default — Android users must manually configure similar protections [1]. A 2025 Privacy International report ranked iOS ahead of stock Android on twelve of fifteen privacy criteria examined, noting that while the gap has narrowed since 2020, it persists on default settings, data retention policies, and cross-service data sharing [2].\nOn the security front, the architecture differs fundamentally:\niOS patches roll out to all supported iPhones simultaneously, the moment a vulnerability is discovered [3] Android patches must filter through device manufacturers and carriers first, creating weeks or months of exposure for non-Pixel devices [3] Apple\u0026rsquo;s App Store enforces a rigorous manual review process, significantly reducing the risk of malware reaching users [4] Features like Face ID, Secure Enclave hardware, and end-to-end iMessage encryption are built into every iPhone [1] Google\u0026rsquo;s Pixel series has narrowed the security gap considerably with its Titan M2 chip and proactive scam-call filtering [2], but iPhone\u0026rsquo;s privacy defaults remain the gold standard for users who don\u0026rsquo;t want to configure their phone like a sysadmin.\nSoftware Updates That Actually Reach You For years, Android\u0026rsquo;s fragmented update delivery was its Achilles\u0026rsquo; heel. While things have improved — Google Pixel 9 and Samsung Galaxy S25 now both promise seven years of OS and security updates [5] — the iPhone\u0026rsquo;s update model still holds key practical advantages.\nApple delivers iOS updates to all supported devices within 24 hours of release, regardless of carrier, region, or manufacturer [6]. There is no middleman. An iPhone purchased in 2019 running iOS 18 receives the same security patch, on the same day, as an iPhone 16 Pro [1].\nPlatform Update Years (Flagship) Simultaneous Rollout? Budget Device Support Apple iPhone 6–7 years ✅ Yes ✅ Yes (same schedule) Google Pixel 7 years ✅ Yes ❌ Limited Samsung Galaxy 7 years (S-series) ❌ Staggered ❌ 2–3 years Other Android OEMs 1–3 years ❌ Staggered ❌ 1–2 years The real-world implication: a mid-range iPhone purchased today will remain secure and feature-current for years longer than its Android counterpart at the same price point [5].\nThe Apple Ecosystem: A Network Effect No Android Brand Can Match Apple\u0026rsquo;s greatest long-term moat isn\u0026rsquo;t any single device — it\u0026rsquo;s the seamless integration across all its hardware. iPhone, iPad, Mac, Apple Watch, AirPods, and HomePod all communicate fluently through features that simply work out of the box [6]:\nAirDrop: Instant, encrypted file transfers to any nearby Apple device — no app or account required Handoff: Start a document, email, or call on iPhone and pick it up on your Mac without skipping a beat Universal Clipboard: Copy text on your iPhone, paste it on your MacBook Continuity Camera: Use your iPhone as a high-quality Mac webcam wirelessly iMessage / FaceTime: End-to-end encrypted messaging and video calls that work natively across all Apple devices Samsung has built a credible ecosystem with Galaxy devices, but it doesn\u0026rsquo;t extend across other Android brands [6]. If you own a OnePlus phone and a Lenovo laptop, you get none of these integrations. Apple\u0026rsquo;s ecosystem works because Apple controls both the hardware and software stack — a constraint that also explains most of iPhone\u0026rsquo;s other advantages.\nSuperior Resale Value: iPhones Hold Their Worth If total cost of ownership matters to you, the iPhone\u0026rsquo;s resale value advantage is striking. On average, iPhones retain 60–70% of their original value after two full years of use [7]. The picture diverges sharply over longer timeframes:\nAfter four years, iPhones still retain roughly 52.5% of original value [7] Flagship Android phones drop to approximately 21.1% over the same period [7] Budget Android phones retain as little as 12.2% [7] The iPhone 15 and 16 Pro models have been shown to keep over 70% of value after just one year of ownership [8]. This means that, when you factor in what you\u0026rsquo;ll recoup at trade-in or resale, the real cost gap between a premium iPhone and a premium Android device is considerably smaller than the sticker price suggests.\nConsistent Performance and Class-Leading Video Apple designs both the iPhone\u0026rsquo;s chip and its operating system, a level of vertical integration no Android OEM can fully replicate. The result is performance that stays smooth across years of use. In 2026 benchmark testing, the iPhone 17 achieves an average gaming frame rate of 130.97 FPS compared to 80.58 FPS on the Pixel 10 [1] — a gap that reflects Apple Silicon\u0026rsquo;s lead in mobile CPU and GPU architecture.\nFor video creators specifically, iPhone Pro models remain the industry benchmark [9]:\nProRes recording for professional post-production workflows Log video capture for flexible color grading in cinema-grade software Cinematic mode for automatic rack-focus shallow depth-of-field video Action mode for heavily stabilized footage without an external gimbal ProRes footage shot on iPhone Pro appears regularly in commercial productions and short films [9] — a creative credential no current Android device can claim at scale.\nApp Store Quality and Developer Priority Developers consistently prioritize iOS when launching new apps and features [4]. The reasons are partly economic (iOS users spend more in apps) and partly logistical — Apple\u0026rsquo;s unified hardware makes it far easier to test and optimize an app than Android\u0026rsquo;s sprawling device fragmentation [4].\nThe practical result for users:\nNew apps typically launch on iOS first, often months before Android versions arrive Apple\u0026rsquo;s strict App Store review process filters out low-quality, insecure, or deceptive apps before they reach users [4] Apple\u0026rsquo;s TestFlight beta platform gives users early access to polished pre-release software in a controlled environment Tools like Xcode and SwiftUI give developers a tightly integrated, consistent development environment that maps cleanly to every iPhone screen size and sensor [4] This developer-first dynamic means iPhone users consistently get access to higher-quality, more stable app experiences sooner.\nThe Bottom Line Android in 2026 is genuinely excellent — especially Pixel and Samsung Galaxy flagships. But iPhone\u0026rsquo;s advantages in privacy defaults, simultaneous software updates, ecosystem cohesion, resale value, sustained performance, and developer mindshare are measurable, not just marketing. For users who want a device that works deeply with other Apple hardware, stays secure without manual configuration, and holds its value over time, iPhone remains the stronger long-term investment.\nSources iPhone vs Android 2026: Privacy, Performance, and Costs iPhone vs Android Privacy and Security in 2026 — Secrets of Privacy Android vs. iOS: Security Comparison 2026 — NordVPN Android vs. iOS App Development: Which is Better in 2026? — Shakuro Android vs iOS Security in 2026: Is iPhone Still Safer? — MacObserver Android vs iPhone Ecosystem in 2026 Compared — Swapper The 2026 Resale Value Report: Which Smartphones Hold Their Worth Best? — iGenius Phone Repair Which Cellphones Hold Their Resale Value the Longest in 2026? — ecoATM Why the iPhone is Better Than Android in 2026 — 73inc ","permalink":"https://cloudmato.com/posts/iphone-advantages-over-android/","title":"iPhone vs Android: 6 Key Advantages of Choosing iPhone"},{"content":"You\u0026rsquo;re a developer testing a local server, or trying to reach an internal corporate tool, and Chrome slams the door with a red \u0026ldquo;Your connection is not private\u0026rdquo; screen. There\u0026rsquo;s a hidden escape hatch baked right into the browser: type thisisunsafe. This article explains exactly what this trick does, where it came from, when it\u0026rsquo;s acceptable to use it — and when it could get you seriously burned.\nWhat Is \u0026ldquo;thisisunsafe\u0026rdquo;? thisisunsafe is a secret keyboard passphrase built into Chromium-based browsers — including Google Chrome and Microsoft Edge — that lets you override SSL/TLS certificate error pages [1]. When Chrome blocks a site due to an invalid, expired, or self-signed certificate and displays errors like NET::ERR_CERT_INVALID or NET::ERR_CERT_AUTHORITY_INVALID, typing thisisunsafe (with the browser window focused, no text field required) instantly dismisses the warning and loads the page [2].\nThe magic is invisible: there\u0026rsquo;s no on-screen text box, no prompt. Chrome listens for the exact keypress sequence in the background. Once you complete the phrase, the block drops and the page renders — though the address bar still shows a \u0026ldquo;Not Secure\u0026rdquo; indicator as a persistent reminder [3].\nCrucially, the bypass is domain-scoped and session-limited. Typing it for https://dev.internal only exempts that exact domain; any other certificate-error site requires re-entering the phrase [3]. It is not a global toggle.\nThe Evolving History of Chrome\u0026rsquo;s SSL Bypass Phrases This escape hatch has quietly existed for years, but Chrome\u0026rsquo;s team has deliberately made it harder to stumble upon by periodically changing the keyword [4]:\ndanger — the original passphrase when the feature was first introduced. badidea — replaced danger in December 2015 after the original phrase spread widely online and was being abused as a casual workaround [5]. thisisunsafe — the current phrase, introduced when badidea itself became too well-known. Internally, Chrome stores it as its Base64 representation (dGhpc2lzdW5zYWZl) to obscure it from casual discovery [4]. The pattern is deliberate. Comments in Chrome\u0026rsquo;s source code state plainly that \u0026ldquo;HTTPS errors are serious and should not be ignored\u0026rdquo; [2]. Each rename is Chrome\u0026rsquo;s way of ensuring that only users who genuinely seek out the bypass — not casual everyday users — can trigger it.\nHow to Use It (Step by Step) Navigate to the site showing a Chrome certificate error page. Make sure the browser window is focused (click anywhere on the page, not in the address bar). Type thisisunsafe — all lowercase, no spaces, no Enter key. The page will reload automatically and load the site past the warning [1]. On Microsoft Edge (also Chromium-based), the same passphrase works identically [3].\nLegitimate Use Cases for Developers The bypass is a genuine productivity tool in specific, controlled scenarios [6]:\nLocal development servers: A self-signed certificate on https://localhost or https://192.168.x.x will trigger Chrome\u0026rsquo;s warning even though you own the server. thisisunsafe gets you past it quickly [2]. Internal corporate tools: Enterprise intranets sometimes run on certificates signed by private CAs not trusted by Chrome\u0026rsquo;s root store. Developers and sysadmins accessing these tools can use the bypass rather than reconfiguring every machine [3]. Security research \u0026amp; proxy tools: Tools like Burp Suite or OWASP ZAP intercept HTTPS traffic via a local proxy, generating certificate warnings that need bypassing during penetration testing sessions [6]. Static informational sites: Browsing a read-only, input-free page where no credentials or personal data are transmitted carries a much lower risk profile [3]. For developers on localhost specifically, a cleaner permanent fix exists: navigate to chrome://flags/, enable \u0026ldquo;Allow invalid certificates for resources loaded from localhost\u0026rdquo;, and relaunch [6]. This avoids the manual bypass entirely.\nThe Real Security Risks You Cannot Ignore The thisisunsafe bypass exists precisely because SSL/TLS certificate warnings protect against man-in-the-middle (MitM) attacks — scenarios where an attacker on the same network intercepts your connection, impersonates the server, and silently reads or modifies all data in transit [7]. Bypassing the warning doesn\u0026rsquo;t make the underlying certificate problem go away; it just tells Chrome to proceed anyway.\nConcrete risks when bypassing on untrusted networks:\nCredential theft: Login credentials and session cookies transmitted over an untrusted connection can be captured in plaintext by an attacker [3]. Malware injection: A compromised intermediary can modify page content to serve malicious scripts from what appears to be a legitimate domain [3]. Undetectable interception: Self-signed certificates provide no external CA validation, making it impossible to distinguish a legitimate server from a forged one [7]. Research from the security firm EMA found that nearly 80% of TLS certificates across the broader internet have configuration vulnerabilities that could expose them to MitM vectors [8] — a sobering reminder that certificate warnings are not false alarms to be casually swatted away.\nAs thisisunsafe has grown in public awareness, security researchers have also flagged the social engineering risk: a malicious actor could instruct a non-technical user to type the phrase, essentially turning a protective barrier into a trojan door [3].\nSafer Alternatives to \u0026ldquo;thisisunsafe\u0026rdquo; If you find yourself reaching for thisisunsafe regularly, it\u0026rsquo;s a sign the underlying certificate issue should be fixed properly:\nInstall a trusted CA for local dev: Tools like mkcert generate locally-trusted development certificates in seconds, eliminating the warning entirely. Use Let\u0026rsquo;s Encrypt: Free, widely trusted certificates for public-facing sites remove the need for self-signed certs [2]. Chrome flags for localhost: The chrome://flags/#allow-insecure-localhost flag is purpose-built for developers and safer than the global bypass [6]. Fix the certificate: If the warning appears on a production site you manage, an expired or misconfigured certificate is a critical issue that should be resolved immediately — not bypassed [9]. The bottom line: thisisunsafe is a sharp tool for a narrow set of developer workflows. Outside that controlled context, it silences an alarm that exists for very good reason.\nSources thisisunsafe – Bypassing Chrome Security Warnings thisisunsafe – How to Bypass Chrome\u0026rsquo;s ERR_CERT_INVALID Warning The Hidden thisisunsafe Bypass: Unlocking Chrome \u0026amp; Edge\u0026rsquo;s Secret SSL Override Chrome\u0026rsquo;s SSL Bypass Cheatcode Bypassing HSTS or HPKP in Chrome Is a badidea Chrome: Bypass NET::ERR_CERT_INVALID for Development How SSL Certificates Help Prevent Man-in-the-Middle Attacks EMA Report Finds Nearly 80% of SSL/TLS Certificates Are Vulnerable to MitM Attacks Chrome Certificate/HSTS Error Bypass Mechanism: In-depth Analysis of \u0026rsquo;thisisunsafe\u0026rsquo; ","permalink":"https://cloudmato.com/posts/thisisunsafe-chrome-ssl-bypass-explained/","title":"thisisunsafe: Chrome's Secret SSL Bypass Explained"},{"content":"React remains the dominant UI library in the frontend ecosystem, and interviewers at startups and FAANG companies alike use React-specific questions to measure depth of knowledge [1]. This guide walks you through the most commonly asked questions in order of difficulty—from entry-level fundamentals all the way to expert-tier architecture and React 19 internals—so you can walk into any frontend interview fully prepared.\nBeginner: Core Concepts Every Candidate Must Know 1. What is React, and what problem does it solve? React is a JavaScript library for building composable user interfaces maintained by Meta. It solves the problem of keeping the UI in sync with application state by tracking changes and updating only the affected parts of the DOM [2].\n2. What is JSX? JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. Tooling like Babel compiles JSX down to React.createElement() calls at build time [2].\n3. What is the difference between props and state? Props are read-only inputs passed from a parent component to a child; state is mutable data managed internally by a component. When state changes, React re-renders the component and its subtree [3].\n4. What is the Virtual DOM? Instead of touching the real browser DOM directly, React first applies changes to a lightweight in-memory representation of the UI. It then diffs the new virtual tree against the previous one and commits only the minimal real DOM mutations required [7].\n5. What are controlled vs. uncontrolled components? In a controlled component, form element values are driven by React state via value and onChange. In an uncontrolled component, the DOM owns the value and you read it via a ref [3].\nIntermediate: Hooks, Lists, and Side Effects 6. Name the core React Hooks and their purpose. React Hooks—introduced in React 16.8—are functions that enable state and lifecycle features in functional components [1]:\nuseState – manage local, simple state useEffect – run side effects such as data fetching or subscriptions useContext – consume a context value without a wrapper component useRef – persist a mutable value across renders without causing re-renders useReducer – handle complex state transitions with explicit action types [2] 7. Why are key props critical in lists? When rendering arrays, React uses key to match elements between renders. Stable, unique keys let React move, insert, and remove items efficiently. Without them, React may unmount and remount items unnecessarily—losing their internal state [1].\n8. How does the useEffect dependency array work? An empty array [] runs the effect once on mount. Listing variables makes it re-run whenever those values change. Omitting the array runs it after every render. Common bugs include stale closures and missing dependencies [4].\n9. When should you use React Context? Context is ideal for global, low-frequency data like authentication status, locale, or theme. However, if data changes frequently—such as form inputs—every consumer re-renders on each change, which can hurt performance [1]. Split contexts by update frequency and memoize provider values when needed.\n10. What is code splitting, and how do you implement it? Code splitting reduces initial bundle size by loading JavaScript on demand. In React, use React.lazy with a dynamic import() and wrap the component in \u0026lt;Suspense fallback={…}\u0026gt; [2].\nAdvanced: Performance, Reconciliation, and Fiber 11. How does React\u0026rsquo;s reconciliation algorithm work? Reconciliation is the process React uses to compute the minimal DOM update when state or props change. React builds a new virtual DOM tree, diffs it against the previous one using an O(n) heuristic (elements of different types produce different trees), and then commits the patch in a single synchronous pass [7].\n12. What is React Fiber? React Fiber is a complete rewrite of the React core algorithm introduced to enable incremental rendering. It breaks rendering into small units of work, allowing React to pause, prioritize, or abort in-progress renders—powering startTransition, useDeferredValue, and the concurrent rendering model [4].\n13. useMemo vs. useCallback—what is the difference? useMemo caches the result of a computation; useCallback caches the function reference itself. Both avoid unnecessary work on re-renders. However, with the React Compiler stable in React 19, most components no longer need these manually—the compiler injects auto-memoization at build time [1]. Reach for them only when profiling reveals a concrete bottleneck [8].\n14. What are custom hooks, and why do they matter? Custom hooks are user-defined functions starting with use that extract and share stateful logic between components without altering the component tree. Common examples include useFetch, useDebounce, and useLocalStorage [3].\n15. When is useReducer preferable over useState? useReducer shines when state has multiple sub-values or when the next state depends on the previous one in non-trivial ways. It keeps transitions explicit, auditable, and easily testable—following the same pattern as Redux [5].\n16. Explain the render and commit phases. React\u0026rsquo;s work is split into two phases: the render phase (reconciliation), which is interruptible and runs pure functions, and the commit phase, which applies the resulting mutations to the real DOM in a single synchronous pass. This split is what makes concurrent mode safe—the commit is always atomic [7].\nExpert: React 19, Server Components, and Architecture 17. What is the React Compiler? The React Compiler is a build-time tool shipped stably with React 19 that automatically injects memoization logic across the component tree. It delivers hand-optimized performance without requiring manual useMemo, useCallback, or React.memo calls in new code [1].\n18. What are React Server Components (RSC)? Server Components render exclusively on the server and are never shipped to the browser. They can access databases and file systems directly and compose seamlessly with Client Components. For any Next.js role in 2026, an inability to distinguish a Server Component from a Client Component is effectively disqualifying [5].\n19. Explain useActionState. useActionState accepts an action function and initial state, returning [state, dispatchAction, isPending]. Dispatching the action sets isPending to true, runs the function, and replaces state with its return value on resolution—replacing what previously required three separate useState calls for data, loading, and error [6].\n20. What is useOptimistic? useOptimistic immediately renders an optimistic version of state while an async action is in-flight, then automatically reverts to the real state once the action settles. It is ideal for likes, chat messages, or list reorders where a network round-trip would feel sluggish [6].\n21. How do you architect a large-scale React application? Senior questions probe architectural judgment, not syntax recall. Key considerations include selecting a state management strategy proportional to complexity (local state → Context → Zustand/Redux Toolkit), leveraging RSC for data-heavy server-rendered views, applying route-level code splitting, relying on the React Compiler for memoization, and enforcing a clean separation between UI components and business logic [5].\n22. How do you approach performance optimization in React? Performance is a measurement discipline first. Start with the React Profiler and bundle analysis tools before reaching for React.memo or virtualization libraries like react-window. Benchmark Time to Interactive (TTI) to know whether a change actually moved the needle [9].\nSources 100+ React Interview Questions Straight from Ex-interviewers (2026) – GreatFrontEnd 70+ React Interview Questions and Answers (2026) – InterviewBit React Interview Questions and Answers (2026) – Toptal 25 React Interview Questions 2026 – Hooks, React 19, Concurrent Mode – DEV Community 50+ Senior React Architect Interview Questions \u0026amp; Scenarios (2026) – Hirecta React 19 Interview Questions – CodifyNext Beyond the Virtual DOM: React\u0026rsquo;s Fiber Architecture and Reconciliation – Medium Master Advanced ReactJS Interview Questions \u0026amp; Answers in 2026 – InterviewKickstart React Coding Interview Questions 2026: 15 Must-Know – Playcode Blog ","permalink":"https://cloudmato.com/posts/react-interview-questions-beginner-to-expert/","title":"React Interview Questions: From Beginner to Expert"},{"content":"The AI landscape in 2026 is both exciting and overwhelming. There are dozens of tools, each promising to transform how you work—but using the wrong one for the wrong job wastes time and money. This guide breaks down the most popular AI tools by category so that whether you are a developer, creator, or curious beginner, you know exactly where to start.\nAI Chat Tools: Your General-Purpose AI Companions Chat-based AI tools are the best entry point for most people. They let you ask questions, draft content, brainstorm ideas, and summarize documents through a simple conversational interface—no technical knowledge required.\nChatGPT (OpenAI) remains the most widely adopted AI assistant, praised for versatility across creative writing, coding help, and research [4]. Its free tier plus $20/month Plus plan unlock GPT-5 and advanced capabilities [5]. Claude (Anthropic) has become the go-to for strict instruction-following, long-document analysis, and reliable coding support—its extended context window lets you feed in entire reports or codebases at once [6]. Gemini (Google) shines for multimodal tasks (analyzing images, PDFs, and audio) and offers the most generous free access of any major platform [5]. Perplexity AI is the best pick specifically for real-time web research: it returns cited, verifiable answers rather than generated text, making it ideal for fact-checking [13]. Grok (xAI), deeply integrated with X (formerly Twitter), excels at real-time social trend analysis and has a strong reasoning engine for math and coding tasks [13].\n→ Use chat tools when: You need a quick answer, want to draft content, or are exploring AI for the very first time.\nAI-Powered IDEs and Coding Assistants If you write code—or want to—these tools dramatically accelerate your workflow by suggesting completions, explaining errors, refactoring functions, and writing entire features from a plain-English description.\nCursor is rated the #1 AI code editor of 2026 for professional developers, featuring multi-file Composer edits and deep codebase indexing at $20/month [1]. Windsurf (by Codeium) is the top pick for beginners and budget-conscious users—it delivers roughly 80% of Cursor\u0026rsquo;s capability at a comparable price, with a friendlier onboarding experience [3]. GitHub Copilot commands over 20 million users and 42% market share among AI coding tools [2]; it integrates seamlessly into VS Code, JetBrains, and Neovim without requiring an editor switch—the safest enterprise choice at $39/month for Copilot Pro+. Claude Code is not an editor but a terminal-based coding agent from Anthropic that handles long-running, multi-file refactors, and is increasingly paired with Cursor by senior engineers for heavy-lifting tasks [1].\n→ Use coding tools when: You write code professionally or are learning to code and want AI that understands your whole project—not just one-off prompts.\nAI APIs: Building on Top of AI Models Developers who want to embed AI capabilities into their own products—apps, bots, data pipelines—use AI APIs. This is the \u0026ldquo;raw ingredient\u0026rdquo; layer of the ecosystem.\nOpenAI API offers the broadest model lineup (GPT-5, o-series reasoning models, DALL-E, Whisper, embeddings) with the most mature SDKs and third-party integrations [9]. Anthropic API (Claude) leads for coding quality and long-context tasks; Claude Sonnet 4 and Opus 4.5 now support 1-million-token context windows, far exceeding OpenAI\u0026rsquo;s standard limits [9]. Google Gemini API holds the largest publicly available context window (over 1 million tokens) and is effectively free for development experimentation via its generous free tier [10]. For enterprises with compliance mandates, AWS Bedrock and Azure OpenAI provide SOC 2, HIPAA, and GDPR-certified access to multiple models with 99.9% uptime SLAs [9].\n→ Use AI APIs when: You are a developer building a product, automation, or service that needs AI capabilities embedded within it.\nAI Agents: Let AI Work Autonomously AI agents go beyond Q\u0026amp;A—they plan, take actions, use tools (browser, code runner, file system), and complete multi-step tasks with minimal human input. This is the fastest-growing category in 2026.\nManus is a general-purpose autonomous agent that decomposes goals into subtasks and executes them using 29 built-in tools including web browsing, coding, and data analysis [7]. Perplexity Computer (launched February 2026) orchestrates 19+ specialized AI models to handle long-running workflows, routing each subtask to the most suitable model [8]. Salesforce Agentforce leads in enterprise agentic work, integrating directly into CRM systems so agents act on live customer data without needing separate integrations [8]. CrewAI is an open-source Python framework favored by developers who want full control over multi-agent pipelines [7]. Grok Build (xAI, launched May 2026) is a terminal-based coding agent for SuperGrok subscribers, aimed at engineers who prefer CLI-first workflows [13].\n→ Use AI agents when: You have complex, multi-step tasks—research, data processing, automated reporting—that would take hours of manual effort.\nAI Creative Tools: Images, Video, and Beyond For designers, marketers, and filmmakers, a dedicated class of generative AI tools handles visual and audio media production.\nMidjourney v8 remains the gold standard for high-quality, aesthetically rich image generation, giving designers granular control over style, composition, and visual consistency [11]. For video, Google Veo 3.1 leads the field in 2026 with superior prompt adherence, native audio synthesis, and cinematic realism [11]. Runway Gen-4.5 and Kling 3.0 are the strongest runner-ups for video with precise stylistic control [11]. One important heads-up: OpenAI shut down Sora in March 2026—its API continues until September 2026, but it is no longer actively developed and should not be the foundation of new projects [12].\n→ Use creative AI tools when: You need to produce images, videos, or audio assets for design, marketing, or storytelling.\nAI No-Code Platforms: Build Apps Without Writing Code By 2026, an estimated 70% of new business applications are being built on low-code or no-code platforms [14]. These tools let you describe what you want in plain English and receive a working, deployable application.\nLovable generates full-stack web apps from natural language prompts, with built-in Supabase database integration and one-click deployment [15]. Base44 takes a fully conversational approach—you chat with the platform and it designs the schema, builds the UI, and handles authentication automatically [15]. Zapier remains the king of workflow automation, connecting 7,000+ apps without a single line of code [14].\n→ Use no-code platforms when: You have a business problem to solve and want a working tool without hiring a developer.\nQuick-Reference: Which Tool for Which Job? Goal Category Top Pick General Q\u0026amp;A / writing Chat ChatGPT or Claude Real-time cited research Chat (search-focused) Perplexity AI Coding inside an editor AI IDE Cursor or GitHub Copilot Building a product with AI API OpenAI or Anthropic API Automating complex workflows AI Agent Manus or Agentforce Creating images / video Creative AI Midjourney / Google Veo 3.1 Building an app, no code No-Code Lovable or Base44 The AI tool you need depends entirely on the job at hand. Start with a chat tool to build intuition, move to specialized tools as your needs grow, and always match the tool\u0026rsquo;s core strength to your actual problem.\nSources Best AI IDEs in 2026: Cursor vs Windsurf vs Copilot vs Zed vs Claude Code vs Codex – DEV Community Best AI Coding Assistants 2026: Cursor vs Copilot vs Claude Code – Scrimba Cursor vs Windsurf vs GitHub Copilot in 2026: Which AI Code Editor Actually Wins? – CodeAnt ChatGPT vs Claude vs Gemini: Which AI Platform Is Best for Business in 2026? – MindStudio Best AI 2026: ChatGPT vs Gemini vs Claude Compared – ITTA ChatGPT vs Gemini vs Copilot vs Claude vs Perplexity vs Grok – Gmelius The Best AI Agents in 2026: Tools and Frameworks Compared – DataCamp The 12 Best AI Agents in 2026: Tested \u0026amp; Reviewed – Lindy Best LLM APIs in 2026: Comparing OpenAI, Claude, Gemini, Azure, Bedrock, Mistral \u0026amp; DeepSeek – Syncfusion The 10 Best LLM API Providers: Which Fits Your AI Workflow? – DataCamp Best AI Video Generator in 2026: Runway, Veo, Seedance, Kling \u0026amp; More – Pixflow Sora Is Dead. Here Are the 7 Best AI Video Generators That Replaced It – Pixo Perplexity vs Grok: The Ultimate 2026 AI Comparison – ClickRank The 8+ Best No-Code App Builders in 2026 – Zapier 9 Best No-Code AI App Builders in 2026: Tested + Compared – Zite ","permalink":"https://cloudmato.com/posts/best-ai-tools-2026-category-guide/","title":"Best AI Tools in 2026: A Beginner's Category Guide"},{"content":"Millions of people use Claude every day — but there\u0026rsquo;s a crucial distinction that trips up beginners and developers alike: a Claude.ai subscription and an Anthropic API key are two entirely separate products with different pricing, access methods, and intended audiences. Understanding which one you actually need can save you money and prevent frustrating \u0026ldquo;why doesn\u0026rsquo;t this work?\u0026rdquo; moments.\nWhat Is the Claude.ai Subscription? A Claude.ai subscription gives you access to Anthropic\u0026rsquo;s conversational AI interface through the web app, desktop client, and mobile apps [1]. Think of it as a Netflix-style service — you pay a flat monthly fee and get a polished, user-friendly chat experience.\nAnthropic currently offers four subscription tiers [2]:\nFree – Limited daily usage, access to Claude\u0026rsquo;s base models. Pro ($20/month) – 5× more usage than Free, priority access during peak times, access to extended reasoning models, Projects, Google Workspace integration, and Claude Code in the terminal. Max ($100–$200/month) – Even higher usage allowances, tailored for power users who live inside Claude all day. Team ($25/seat/month, min. 5 seats) – Collaboration features, centralized billing, and admin controls for organizations. Enterprise – Custom pricing with SSO, enhanced security, and dedicated support. The subscription is designed for knowledge workers — writers, analysts, students, researchers, and anyone who needs an AI thinking partner through a browser or desktop interface [3].\nWhat Is the Anthropic API and the API Key? The Anthropic API is an entirely separate product aimed at developers and businesses who want to embed Claude\u0026rsquo;s intelligence directly into their own applications, scripts, or automated workflows [4]. Instead of a monthly flat fee, you pay per token — small chunks of text that Claude processes.\nAccess is managed through the Anthropic Console at platform.claude.com, where you create an account, add a payment method, and generate an API key. All Claude API keys begin with the prefix sk-ant- and are shown only once at creation — if you close the dialog without copying it, you must revoke and regenerate [5].\nCurrent API token rates as of May 2026 are [6]:\nModel Input (per 1M tokens) Output (per 1M tokens) Claude Haiku 4.5 $1.00 $5.00 Claude Sonnet 4.6 $3.00 $15.00 Claude Opus 4.7 $5.00 $25.00 Developers can dramatically reduce costs using Prompt Caching (cuts cached-input cost by up to 90%) and the Batch API (50% cheaper for asynchronous, non-real-time jobs) [6].\nThe Critical Distinction: They Are NOT Interchangeable This is the point that catches most newcomers off guard: a paid Claude.ai subscription does not include Claude API access, and vice versa [7]. Anthropic explicitly confirms that the Pro, Max, Team, and Enterprise plans cover the claude.ai chat experience only. If you want programmatic API access through the Console, you must set up a separate Console account with its own billing — even if you\u0026rsquo;re already paying for Pro [7].\nIn practice, this means:\nLogging into claude.ai and chatting → billed through your subscription. Calling api.anthropic.com in your Python/Node.js code with an sk-ant- key → billed through your Console API credit balance [4]. The two products do not share a wallet.\nPricing Philosophy: Flat Fee vs. Pay-Per-Token The subscription model suits predictable, human-paced usage. You know exactly what you\u0026rsquo;ll pay each month, and heavy conversational use (dozens of long chats per day) often delivers far more value than equivalent API spend [8]. Analysts at mem0.ai estimate that Pro users can get the equivalent of roughly $150 worth of API tokens for just $20/month in casual chat usage [9].\nThe API model suits variable, programmatic workloads. If you\u0026rsquo;re running 10 million tokens per day through Sonnet 4.6, you\u0026rsquo;ll spend roughly $90/day at standard rates — a subscription wouldn\u0026rsquo;t cover that use case at all [9]. But for low-volume automations or occasional scripts, the pay-as-you-go model means you pay nothing when you\u0026rsquo;re idle.\nWho Should Use Each? Choose a Claude.ai subscription if you:\nPrimarily use Claude through a browser, desktop, or mobile app. Do human-led writing, research, analysis, or brainstorming. Want a predictable monthly bill without tracking tokens. Need Claude Code in the terminal covered under a flat fee [10]. Choose the Anthropic API (with an API key) if you:\nAre a developer building an app, chatbot, or automated pipeline. Need to call Claude programmatically from code (Python, Node.js, etc.). Run CI/CD pipelines, headless automation, or agent frameworks. Want fine-grained control over which model, temperature, and context window you use [3]. How to Get an Anthropic API Key Getting started with the API takes under five minutes [5]:\nGo to platform.claude.com and sign up with your email or Google account. Navigate to Billing and add a credit card (a $10–$25 starting credit is common for testing). Click API Keys in the left sidebar, then Create Key. Name the key descriptively (e.g., my-app-production), then copy it immediately — Anthropic does not store the full key value after this point. Use the key in your code via the x-api-key header or Anthropic\u0026rsquo;s official SDKs for Python and TypeScript. For most solo developers, starting with a small credit balance and monitoring usage in the Console dashboard is the safest approach before committing to larger workloads.\nSources What is the Pro plan? | Claude Help Center Plans \u0026amp; Pricing | Claude by Anthropic Claude, Claude API, and Claude Code: What\u0026rsquo;s the Difference? API overview - Claude API Docs How to Get a Claude API Key: Complete Guide (2026) - DEV Community Pricing - Claude API Docs I have a paid Claude subscription. Why do I have to pay separately for the API? | Claude Help Center Claude Pro vs API: Which Is Right for You? | Pine AI Claude Pricing: Every Plan and API Cost (May 2026) Claude Pricing In 2026: Every Plan, API Cost, And Optimization Strategy Explained ","permalink":"https://cloudmato.com/posts/claude-subscription-vs-anthropic-api-key/","title":"Claude Subscription vs Anthropic API Key: Key Differences"},{"content":"MacBooks are sleek, powerful, and undeniably popular — but they\u0026rsquo;re far from perfect. Whether you\u0026rsquo;re a frustrated Apple owner or someone deciding between platforms, this guide breaks down the most common MacBook problems and the key areas where Windows laptops come out on top.\nCommon MacBook Problems You Should Know About Despite their popularity, like any technology product, MacBooks come with their share of issues. Here are the most frequent pain points users report:\nOverheating \u0026amp; Thermal Throttling\nMany MacBook Pro models with Intel processors struggled with overheating, especially during demanding tasks like video editing or gaming. This led to reduced performance as the system throttled to manage the heat. Even on newer Apple Silicon models, overheating remains a frequent issue. The bottom of the laptop becomes very warm, fans ramp up loudly on Intel models, and the system may feel slow. Common causes include heavy CPU-intensive apps, poor ventilation, or using the MacBook on soft surfaces that block vents.\nFast Battery Drain\nFast battery drain is among the most worrying MacBook problems because users rely on these laptops for all-day work. When battery life drops suddenly, it often comes down to display brightness, background apps, or an aging battery reaching the end of its cycle life. In fact, Apple says Mac notebook batteries are generally designed to retain up to 80% of their original charge capacity at their maximum cycle count, which for many modern models is 1,000 cycles.\nConnectivity \u0026amp; Wi-Fi Issues\nCommon problems on a MacBook include Wi-Fi and Bluetooth connection issues, battery drain, slow performance, and a sluggish internet browser. MacBook users have occasionally encountered Wi-Fi issues such as intermittent disconnections or weak signals, especially after macOS updates.\nMysterious Storage Problems\nReal-world users continue to report head-scratching storage behavior. One user reported that on boot, the system showed 380 GB of storage used with only ~100 GB remaining — and after a few minutes, 120 GB was attributed to \u0026ldquo;System Data\u0026rdquo;, with no clear path to reclaim it.\nKeyboard Failures\nThe butterfly keyboard, used in MacBook models from 2015 to 2019, became infamous for its design flaws. Keys could easily get stuck, fail to register, or even type double due to dust and debris accumulation under the keys. Apple eventually launched a free repair program for affected keyboards and later transitioned back to a more reliable scissor mechanism in newer models.\nDisplay Issues (Flexgate)\nSome MacBook Pro models (2016–2018) suffered from a display issue known as \u0026ldquo;Flexgate,\u0026rdquo; where the screen\u0026rsquo;s backlight would fail, causing a \u0026ldquo;stage light\u0026rdquo; effect or complete display failure due to fragile cables.\nSlow Performance Over Time\nOne of the most common Mac issues is a MacBook that feels sluggish. Apps take longer to open, the beachball appears frequently, and simple tasks start to drag. This usually happens when storage is nearly full, too many apps launch at startup, or the system has not been restarted for a long time.\nWhere Windows Laptops Are Clearly Better 1. Hardware Flexibility \u0026amp; Upgradability Macs are basically impossible to upgrade on their own. Memory and hard drives are usually soldered to the motherboard from the factory, and you have to pick the right configuration when you buy it. On the other hand, many Windows PCs — especially desktops and some high-end laptops — allow users to replace RAM, hard drives, and even graphics cards at a later date, making it easier to improve performance and extend the life of the device.\n2. Price \u0026amp; Value for Money Apple\u0026rsquo;s MacBooks are pretty expensive compared to many high-end Windows devices. Apple devices are superior in build quality, design, and support, but prices are too high if you are looking for something on a budget. On the other hand, Windows devices are affordable and cater to every type of user, from a student looking for a budget option to a professional graphic designer looking for a high-performing workstation. As one longtime user summarized, you can buy a Windows laptop that delivers similar performance to a MacBook at a fraction of the price, and you also have the option to get an upgradeable Windows laptop, allowing you to use it for years to come.\n3. Superior Gaming Performance Since games are largely written for PC, developers use tried and tested methods to streamline their games and make them run better. Releasing on Mac incurs extra costs for developers and publishers — not only will they have to rewrite code to make the game run properly for Mac, but they\u0026rsquo;ll also reach a smaller market. While Apple Silicon chips perform well, the number of games available on macOS is far fewer than on Windows, and many games are not optimized for macOS.\n4. Broader Software Compatibility Windows is the most popular desktop OS, and its widespread popularity means software developers are inclined towards creating more Windows-specific apps. This makes Windows compatible with a vast collection of software that macOS isn\u0026rsquo;t. Whether you\u0026rsquo;re looking for productivity tools, creative software, or niche applications, there\u0026rsquo;s a high chance you\u0026rsquo;ll get a Windows version. You can really do everything with a Windows laptop: there is suitable software for every purpose — from productive programs for work, study, and school to creative applications in the multimedia field. Thanks to this large selection, you can choose among several alternatives within each software category.\n5. More Ports \u0026amp; Better Connectivity When it comes to ports and connectivity, MacBooks tend to maintain a minimalist approach. While you can appreciate Apple\u0026rsquo;s sleek design, this can also lead to some practical drawbacks. If you have devices requiring connection — external hard drives, USB flash drives, printers, and more — the limited number of USB ports might leave you feeling like your options are severely limited. By contrast, most Windows notebooks still offer at least one USB Type-A socket, which is surprisingly helpful in everyday life. Gaming notebooks also almost always have an Ethernet port with Gigabit or 2.5Gbit speed — the best option for a fast and stable network connection.\n6. Touchscreens, 2-in-1s \u0026amp; Form Factor Variety You get a lot more choice in design and color with Windows than you do with MacBooks. Windows laptops come in many more form factors, such as tablets, convertible 2-in-1s, dual-screen laptops, and more. MacBooks are limited to clamshells only, and only Windows laptops have touch- and pen-enabled displays.\n7. More Powerful Developer Tools \u0026amp; Hardware Options With the introduction of WSL2 (Windows Subsystem for Linux 2), Windows is no longer a second-class citizen in developer environments — it\u0026rsquo;s a powerful hybrid platform. WSL2 allows you to run Ubuntu, Debian, or other distros inside Windows like they were native apps. High GPU and hardware flexibility also mean you can build or upgrade machines to meet your specific developer needs, especially for game development, 3D modeling, or AI/ML training.\n8. Customization Freedom One of the biggest flexes Windows users enjoy is the ability to customize or upgrade their system to their liking using built-in options and third-party tools. While Apple tightly binds macOS, Windows users can personalize most aspects by tweaking registries and making system-level changes through apps.\nThe Verdict You should choose Windows if you need gaming performance, want hardware customization, prefer cheaper price points, or use enterprise software. Meanwhile, Mac laptops are better suited for users seeking efficiency, ecosystem seamlessness, and battery life. For most everyday users, professionals, and gamers, Windows 11 stands out for its adaptability, extensive software compatibility, and vast hardware options offered by renowned brands like HP, Dell, and ASUS. The MacBook is a refined machine — but if flexibility, value, and power matter most to you, Windows remains the smarter choice.\nSources Common Issues with Apple MacBooks — Des3Tech Problems with MacBooks that Lead to Repairs — TechToro 10 Common iMac and MacBook Problems with Solution — Stellar Info The 5 Most Common MacBook Problems (And How To Fix Them) — BGR MacBook Pro 2024 Storage Issues — Apple Community 10 MacBook Problems Users Face Most — TechTimes Common MacBook Problems and How to Fix Them — Box.co.uk 9 Reasons Why Windows Is Still Better Than macOS — XDA Developers Mac vs Windows Showdown — Vibetric Windows 11 vs. macOS 2025: A Comprehensive Comparison — Windows Forum Apple Mac vs. Windows Laptops in 2026: The Ultimate Comparison — TechnoReviewers What Are The Disadvantages Of A MacBook Over Windows? — Medium How to Choose Between a MacBook and a Windows Laptop — Digital Trends Mac vs. Windows PC: A Complete Comparison — ACEMAGIC Mac vs Windows for Developers in 2025 — Medium Windows Laptops vs. MacBooks: 5 Compelling Reasons to Go With a PC — PCWorld I Switched to a MacBook After Using Windows for Over 30 Years — Laptop Mag Settling The Score: Mac vs PC for Gamers and Professionals in 2025 — Eneba ","permalink":"https://cloudmato.com/posts/macbook-problems-where-windows-is-better/","title":"MacBook Problems \u0026 Where Windows Simply Does It Better"},{"content":"This blog is generated by Claude with the web_search tool, written into Markdown, and rendered by Hugo.\nHow it works You open a local page, type a topic, and Claude does the research, the writing, and the citations [1]. The post then lands in content/posts/ and is pushed to git in one step.\nSources Hugo documentation ","permalink":"https://cloudmato.com/posts/welcome/","title":"Welcome to Auto Blog"},{"content":" cloudmato About cloudmato.com cloudmato.com is a research-driven blog where every article is backed by live web sources — no filler, no guesswork.\nThe site covers web development, browser internals, networking, security, databases, distributed systems, and the AI tools and models reshaping how software gets built.\nWhy this blog? Good technical writing is rare. Most articles either skim the surface or bury the insight under walls of marketing copy. cloudmato.com exists to cut through that — plain language, real examples, cited sources.\nTopics covered Web development, frontend frameworks, and browser DevTools Networking, HTTP, DNS, and web security Databases, caching, and distributed systems AI tools, LLM APIs, and how to use them effectively Cloud infrastructure, DevOps, and developer tooling Get in touch Have a topic you\u0026rsquo;d like covered? Spotted something that needs a correction?\nReach out at cloudmato@gmail.com\nEvery article on this site is written with cited web sources. Links to sources appear inline as numbered references.\n","permalink":"https://cloudmato.com/pages/about/","title":"About"}]