Showing posts with label website. Show all posts
Showing posts with label website. Show all posts

Good enough is not good enough

Mike's Notes

This gem from Roberto Di Cosmo, Director of Software Heritage, explains much of the web's history and the current swarm of AI crawlers.

"The "good enough" trap: Why the web keeps breaking itself

Every day, swarms of AI crawlers storm the web like "digital locusts," repeatedly re-downloading entire websites just to discover what changed. It's wildly inefficient, drives up massive infrastructure costs, and is actively forcing the open web to lock its doors behind bot filters and API paywalls.

The most frustrating part? We solved this decades ago. In his new series, Roberto Di Cosmo, Director of Software Heritage, traces how our digital infrastructure keeps falling into the "good enough" trap: local decisions that work fine for individual actors, but compound into an aggregate disaster for everyone else.

Back in 1998, long before Google dominated search and decades before LLMs arrived, Di Cosmo co-authored an IETF Internet-Draft proposing a simple, push-based alternative called the Remote Update Protocol (RUP). A web server knows when its data changes, so by broadcasting updates directly, it spares crawlers the endless need to ask, “Anything new?"

Despite being technically sound and independently re-invented by others, the protocol quietly expired. It lacked a dedicated institution to champion, maintain, and deploy it. Fast-forward to today, and everyone pays the price for relying on "good enough" brute-force scraping.

The mission of Software Heritage is to address this directly by archiving public source code once so the world doesn't have to collect it over and over. 

Di Cosmo’s series isn't just a historical autopsy—it’s a warning. Without collective, sustained investment in shared digital infrastructure, the open web will continue to disappear into proprietary silos." - Software Heritage

Resources

References

  • Consent in Crisis: The Rapid Decline of the AI Data Commons, Data Provenance Initiative, July 2024 (arXiv:2407.14933).
  • R. Di Cosmo and P. E. Martínez López, “Distributed Robots: a Technology for Fast Web Indexing”, written January 1998.
  • Hijacking the World: the dark side of Microsoft, October 1998.

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Software Heritage
  • Home > Handbook > 

Last Updated

28/08/2026

Good enough is not good enough

By: Roberto Di Cosmo
Roberto Di Cosmo: 08/2026

An alumnus of the Scuola Normale Superiore di Pisa, with a PhD in Computer Science from the University of Pisa, Roberto Di Cosmo was associate professor for almost a decade at Ecole Normale Supérieure in Paris. In 1999, he became a Computer Science full professor at University Paris Diderot, where he was head of doctoral studies for Computer Science from 2004 to 2009. President of the board of trustees and scientific advisory board of the IMDEA Software institute and chair of the Software chapter of the National Committee for Open Science in France, he is currently on leave at Inria.

His research activity spans theoretical computing, functional programming, parallel and distributed programming, the semantics of programming languages, type systems, rewriting and linear logic, and, more recently, the new scientific problems posed by the general adoption of Free Software, with a particular focus on static analysis of large software collections. He has published over 20 international journals articles and 50 international conference articles.

In 2008, he has created and coordinated the european research project Mancoosi, that had a budget of 4.4Me and brought together 10 partners to improve the quality of package-based open source software systems.

Following the evolution of our society under the impact of IT with great interest, he is a long term Free Software advocate, contributing to its adoption since 1998 with the best-seller Hijacking the world, seminars, articles and software. He created in October 2007 the Free Software thematic group of Systematic, that helped fund over 50 Open Source research and development collaborative projects for a consolidated budget of over 200Me. From 2010 to 2018, he was director of IRILL, a research structure dedicated to Free and Open Source Software quality.

He created in 2015, and now directs Software Heritage, an initiative to build the universal archive of all the source code publicly available, in partnership with UNESCO.

...

A series about why the open web is collected so badly, why that has survived thirty years of everyone knowing better, what it is now costing all of us, and what is needed to go from “good enough” to “doing things right”.

What this is

The series runs on LinkedIn. This page is where it lives afterwards: every post in order, and under each one the sources for every claim it makes — dates, references, links, commit identifiers, intended to make it possible for you to check the facts directly.

It is also here for a more practical reason. What follows is partly about things quietly disappearing from the web, and about what it costs to depend on a single platform. Leaving the only copy inside somebody else's feed would have made the point rather too well.

The posts

  • 01 The wasteful sentence 4 Aug
  • 02 The alternative was written down 11 Aug
  • 03 How good enough wins 18 Aug
  • next: The mechanism: an externality, not a villain

The series

POST 01 The wasteful sentence 4 August 2026

The words "This way of collecting data is very wasteful", quoted from David Senecal, principal product architect for fraud and abuse at Akamai, Business Insider, 19 September 2024.

This way of collecting data is very wasteful.

That is how an Akamai specialist described the way many AI crawlers gather material from the web.[1] The report around the quotation gives the concrete mechanism: some botnets revisit an entire site every day merely to discover what changed, transferring the same material again and again. This is not new: search engines have been doing it for decades, in the very same wasteful way, but the scale of "scraping" is now such that what we have known as the "open web" is now closing down, an API at a time[2], a "bot filter" at a time[3], effectively reducing the global value for everybody, while increasing the waste of resources.

The striking part is not that the method is wasteful. Everybody running a sizeable website knows that by now. The striking part is that we speak as though this were a law of nature.

It is not. We knew how to avoid most of this waste in the 1990s, and it turns out I actively tried to push forward a concrete solution, when the problem was still manageable. We'll get back to this.

For nearly three decades, I have repeatedly encountered the same pattern: a system that works well enough for every actor taken separately, while imposing a growing cost on everybody taken together. Each local decision is reasonable. Their aggregate result is damaging. Since nobody owns the aggregate problem, the better solution remains nobody's job.

It is time to call out this state of affairs, and act upon it... again: I personally tried many times, and now I can build on that experience. The first step is to clearly understand what is happening and why: which actor does what and why, who gains, who loses. In the coming weeks I will reconstruct one instance of the pattern from dated documents. It begins with web crawlers and ends with the institutions we build—or fail to build—for shared digital infrastructure.

Let me point at the destination from the outset: I direct Software Heritage, a non-profit organisation that collects public source code once so that others do not all have to collect it again. This is my latest effort to contribute to systematically address this long standing problem.

It is a problem that starts with two simple words: good enough.

POST 02 The alternative was written down 11 August 2026

A record card for the Internet-Draft "Distributed Robots: a Technology for Fast Web Indexing", by Di Cosmo and Martinez Lopez, recorded 4 March 1998, the document printing "EXPIRES SEPT 1998" with no day given. Beneath the card, a caption reads: "What today's AI crawlers do, answered on paper in 1998."

One of my first encounters with the "good enough" curse came in the mid-nineties, during the ferocious battle to control the rapidly expanding cyberspace.

WebCrawler, AltaVista, Alexa, Yahoo… Google, the eventual winner, had not even been born yet.

What stunned me was that all of them did essentially the same thing: download the Web again and again to discover what had changed.

Sounds familiar? It should: it is exactly what AI crawlers do now.

The Web was much smaller then, but bandwidth, compute and storage were scarcer too, so the waste was already real.

And there was an obvious alternative.

In January 1998, with Pablo E. Martínez López — aka Fidel — I wrote Distributed Robots: a Technology for Fast Web Indexing. It entered the IETF record as an individual Internet-Draft on 4 March 1998,[4] carrying the wonderfully explicit line: "EXPIRES SEPT 1998."

The idea was elementary, familiar to any systems programmer: interrupt-driven beats busy-wait.

A web server knows, cheaply, when its files change. Let it say so, and let crawlers fetch only what is new — instead of asking every server, over and over, "Anything changed?"

In May 1998 an IETF Area Director sent us a generous, detailed critique. By then I had moved on: that March I had put Piège dans le Cyberespace online — CyberSnare in English — which went viral, started thirty years of work on free and open source software, and became a book with Dominique Nora.[5]

It took me seven months to answer point by point. I first asked what he thought of the revised structure.

The answer was an automatic out-of-office message. The thread ended there.

We produced the revised draft anyway, dated 20 April 1999, incorporating the review.[6] Fidel sent it to the RFC Editor. No answer came.

But the problem had not disappeared. In 2001 the IETF's own WEBI group independently produced Requirements for a Resource Update Protocol.

That expired too.[7]

Years later, looking at my web server logs, I found that in 2009 Googlebot had fetched one unchanged page twenty-four times.[8]

Students of mine implemented the Remote Update Protocol twice, in Java and OCaml.[9] The technical idea was not the hard part.

And this is the point: a better technical solution does not deploy itself.

Someone must maintain it, persuade others to adopt it, integrate it into existing systems, and keep pushing after the prototype works. Advocacy on the side of a full-time job is not an institution.

The alternative was written down almost thirty years ago, then rediscovered independently inside the IETF.

It was not defeated by a better idea.

It was not shown to be technically impossible.

It simply never acquired enough organised support to become infrastructure.

Meanwhile, the "good enough" solution kept scaling.

Why?

POST 03 How good enough wins 18 August 2026

A French sentence, "le systeme actuel fonctionne assez bien", above its footnote, "Good enough, comme on dit chez nos amis anglosaxons." From an article revised into 2011, written up from a talk at Inria in December 2007.

So why did it survive?

Not because anyone defended it. Because it was made liveable, one patch at a time.

Let's be precise about what "good enough" means: it does not mean bad. A good enough system does not do the right thing, but it does get the thing done. It is a hack, a wooden leg that gets you across the room… And because it gets you across the room, you stop looking for a better leg.

Downloading the whole web to find out what changed was never sensible. It was patched until it was bearable.

In 1994 Martijn Koster proposed robots.txt, so a site could say: not this, not here. It worked well enough that nobody standardised it until 2022.[10]

In 2006 came sitemaps, so a site could say: here is what I have, and here is when it last changed.

Read the specification closely. The freshness field is "a hint and not a command", and crawlers may ignore it.[11] So a site can say exactly what changed, and you are free not to believe it and download everything anyway. Which is what happened.

Then the crawlers got cleverer about where to spend. Google's own documentation says it plainly: "URLs that are more popular on the Internet tend to be crawled more often."[12] And if your server struggles, the crawler backs off.

Add it up and you have a system that works. Big sites get visited often, small sites rarely, a struggling server gets a break. Nobody is delighted, but nobody is ruined.

That is what good enough looks like from the inside: not a catastrophe, but a tolerable arrangement everyone has adapted to.

And then the second thing happens, which is worse: it becomes normal.

You are almost certainly reading this on a QWERTY keyboard. Nobody chose it this morning. Everybody knows it, every keyboard has it, and whoever switches first pays the whole cost alone. So it stays: not because anyone re-decided it, but because it is installed.

That is where web crawling ended up. Installed. It always worked like this.

In December 2007 I gave a talk at Inria's fortieth anniversary in Lille, written up as an article I revised into 2011. I wrote that the current system was working « assez bien ».[13] French had no phrase for what I meant, so I borrowed one, in a footnote: « Good enough, comme on dit chez nos amis anglosaxons. »

I was not complaining. I was describing. It was accurate.

Lock-in is harmless when the stakes are low. Nobody is much hurt by a keyboard layout.

Repeated crawling is not. It spends bandwidth, electricity and machine time on a planetary scale to fetch what has not changed.

Thirty years on from robots.txt, the patches are not holding, and everyone can see it.

So the question is not why nobody noticed the waste. Everybody noticed.

The question is why a cost that large stays invisible to the people who could act on it, until it is too late to act easily.

That is not a technical question. Economists have a name for it, and it is the whole point that we need to delve into.

References

Every source for every claim, numbered in the order the posts cite them. Each one carries a way back to the place it was cited from.

[1] post 01 Darius Rafieyan, “Like digital locusts, OpenAI and Anthropic AI bots cause havoc and raise costs for websites”, Business Insider, 19 September 2024. Archived copy: annex.softwareheritage.org. The speaker is David Senecal, principal product architect for fraud and abuse at Akamai. The sentence in full: “This way of collecting data is very wasteful,” he said, “but until the mindset on data sharing changes and a more evolved and mature way to share data exists, scraping will remain the status quo.” The sentence that follows in the post — botnets crawling a whole site daily — is the reporter’s prose summarising him, not a quotation, and is given as reported speech for that reason. ↩ back to the text

[2] post 01 GitHub tightened its limits on unauthenticated access on 8 May 2025, citing “an increase in scraping activity targeting our API”. Smaller operators went further: SourceHut placed a proof-of-work challenge in front of its web interface after a week-long crawler incident in March 2025, and GNOME, KDE, Fedora and Codeberg have each done some version of the same. ↩ back to the text

[3] post 01 Longpre et al., Consent in Crisis: The Rapid Decline of the AI Data Commons, Data Provenance Initiative, July 2024 (arXiv:2407.14933) — an audit of ~14,000 web domains behind three widely used AI training corpora, finding a sharp rise in crawl restrictions across a single year. ↩ back to the text

[4] post 02 R. Di Cosmo and P. E. Martínez López, “Distributed Robots: a Technology for Fast Web Indexing”, written January 1998. Record: datatracker.ietf.org. Full text, with everything that followed it: dicosmo.org/RUP/. The document prints “EXPIRES SEPT 1998” and gives no day, so none is claimed here. ↩ back to the text

[5] post 02 « Piège dans le Cyberespace », and the book that came out of it. The essay appeared in Multimédium (Canada) on 17 March 1998. It is online free and complete, in five languages, at dicosmo.org/Piege/cybersnare/ — French, English as CyberSnare, German as Falle im Cyberspace, Italian as Trappola nel Cyberspazio, Spanish as Trampa en el Cyberespacio — with a Chinese version at PiegeCN.html. The book is Le Hold-up planétaire : la face cachée de Microsoft, with Dominique Nora, Calmann-Lévy 1998, ISBN 2-7021-2923-4; Kirk McElhearn’s English translation, Hijacking the World: the dark side of Microsoft, followed in October 1998. When the publisher stopped reprinting in July 2006 the two authors recovered their rights and put the book out under Creative Commons Attribution-NonCommercial-NoDerivs. dicosmo.org/HoldUp/ carries the French, English and Spanish texts in full — the Spanish, El Asalto Planetario, never having had a print edition at all. ↩ back to the text

[6] post 02 The review, the resubmission, and the silence. On 10 May 1998 an IETF Applications Area Director reviewed the draft in the IESG and asked that the objects be defined as MIME types and distinguished from the FIND working group’s CIP and SOIF and from the W3C’s RDF. The point-by-point answer went back on 8 December 1998, accepting the MIME and SOIF encapsulation and asking one question before resubmitting; the reply, ten seconds later, was an automatic out-of-office, and no further correspondence followed. draft-RUP-01.txt, The Remote Update Protocol (RUP). Part I: RUP Architecture, is dated 20 April 1999 and does what the review asked. It was submitted: Martínez López sent it to the RFC Editor on 16 April 1999, with the file attached, describing it as “an update for the internet-draft <draft-rfced-exp-cosmo-00.txt>”. No reply ever came — the same address had answered the 1998 submission twice within hours — and six days later he wrote asking whether to chase them or wait a little longer. No IETF record of draft-RUP-01 exists. Nobody behaved badly: a volunteer reviewer was away for a fortnight, the answer had taken seven months, and after that carrying it forward was nobody’s job. The correspondence is in the author’s own archive. ↩ back to the text

[7] post 02 Twenty-four Googlebot fetches of one unchanged page across 2009, twelve of them answered 304 Not Modified. The log is reproduced in full on page 9 of the Inria anniversary article, which is now online with the talk it came from: dicosmo.org/Inria40/. ↩ back to the text

[8] post 02 “Requirements for a Resource Update Protocol”, draft-ietf-webi-rup-reqs, IETF WEBI (Web Intermediaries) working group. The first revision is M. Hamilton (JANET Web Cache Service) and I. Cooper (Equinix), 22 February 2001; Dawn Li and Mike Dahlin joined later; the last revision is dated 4 March 2002, and it expired. Its consumers are caching proxies and surrogates, not crawlers — a different protocol for a different audience. What is shared is the diagnosis, in its own abstract’s words: such a protocol is needed where “periodic revalidation is unacceptable in terms of performance and/or cache consistency”. There is no evidence the two efforts knew of each other, and none is claimed: the point is that the same conclusion was reached twice, independently, and expired twice. ↩ back to the text

[9] post 02 Both implementations were student projects at Paris 7 — Travaux d’Étude et de Recherche on the subject “indexation rapide du Web”, which Roberto set for two years running. The RUP 1.0 Java servlet, by Yerom-David Bromberg, is at dicosmo.org/RUP/RupJava/. OCamlRup — a RUP client, a generic RUP server and an Apache CGI server, with rupinfo.txt parsing and robots.txt integration — was written in 2001 by Samuel Lasry and Xavier Patourel and is archived at ocamlrup-0.1.tar.gz. Copyright remains the authors’; no licence was ever attached and none is asserted — it is published as an archival record, with attribution. ↩ back to the text

[10] post 03 robots.txt. The Robots Exclusion Protocol was, in the words of the RFC that eventually specified it, “originally defined by Martijn Koster in 1994 for service owners to control how content served by their services may be accessed”. It became an IETF standards-track document only in September 2022: RFC 9309, by Koster with G. Illyes, H. Zeller and L. Sassman. Twenty-eight years of running on a convention. ↩ back to the text

[11] post 03 Sitemaps, and the word “hint”. The Sitemap protocol 0.9 says of changefreq: “Please note that the value of this tag is considered a hint and not a command.” It goes on: crawlers “may crawl pages marked ‘hourly’ less frequently” than stated. The protocol lets a site describe its own freshness; it obliges no one to act on the description. That asymmetry is the whole of this post. ↩ back to the text

[12] post 03 Crawlers spending where it pays. Google’s own documentation on managing crawl budget: “URLs that are more popular on the Internet tend to be crawled more often to keep them fresher in our systems.” And on backing off: if a site “slows down … or responds with server errors … the limit goes down and Google crawls less.” Adaptation, not repair — the polling continues, more politely. ↩ back to the text

[13] post 03 From a talk given at Inria’s fortieth anniversary, Lille, December 2007, written up as an article revised into 2011: « le système actuel fonctionne assez bien », with the footnote “Good enough, comme on dit chez nos amis anglosaxons.” Both are now online at dicosmo.org/Inria40/ — the slides as the audience saw them, and the article, which was never finished; the footnote is on page 10. The article is cited as talk December 2007, article revised into 2011; no span of years is computed from it anywhere in this series. ↩ back to the text

Corrections

Every correction made after publication is logged here, with its date and what changed. Nothing is silently edited. If you find an error, the fastest way to have it fixed is to tell me.

— nothing logged yet —

www.dicosmo.org/good-enough/ — the version of record
Roberto Di Cosmo · dicosmo.org
Text licensed CC BY 4.0. Quoted material remains its authors'.
This page loads nothing from any other host: no fonts, no scripts, no analytics.

cf-indexnow: Submit URLs to search engines from ColdFusion

Mike's Notes

Useful. Filed away for future implementation.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > CFBreak
  • Home > Handbook > 

Last Updated

21/08/2026

cf-indexnow: Submit URLs to search engines from ColdFusion

By: James Mobeg
MyCFML: 19/08/2026

myCFML is the developer Blog of James Moberg, Senior Web Application Developer at SunStar Media. Featuring CFML-related articles and best practices.

IndexNow is an open protocol introduced by Microsoft Bing and Yandex in 2021. It lets a website notify search engines the moment a URL is added, changed, or removed instead of waiting for the next crawl. You POST a list of URLs along with a key that proves you control the host, and the submission is automatically shared with every participating engine: Bing, Yandex, Seznam, Naver, and Yep. (Google doesn't participate, but Bing results also feed DuckDuckGo, Yahoo, and others, so one ping still covers a lot of ground.)

I've published cf-indexnow, a CFC that implements the protocol for Adobe ColdFusion 2016+ and Lucee 5+. It's MIT licensed and I'm using it in production. IndexNow.cfc is the only required file:

indexNow = new IndexNow( host="www.example.com", key="yourindexnowkey" );
result = indexNow.submitUrl( "https://www.example.com/new-article" );
if ( !result.success ) {
writeLog( file="indexnow", text=result.message );
}

Network problems and API rejections never throw. Every submission returns a struct with success, the HTTP statusCode, a plain-English message, how many URLs were submitted, which input URLs were skipped, and the raw per-batch responses. Invalid constructor arguments do throw (with typed exceptions like IndexNow.InvalidKey), so configuration mistakes surface during development rather than in a production log.

submitUrls() accepts an array and automatically splits anything over 10,000 URLs into multiple POSTs, which is the protocol's per-request limit. submitSitemap() fetches a sitemap.xml, recurses into sitemap index files, and submits every URL it finds. In both cases, URLs that don't belong to the configured host are filtered out and reported in the result instead of being submitted, because a single foreign URL will get an entire batch rejected with a 422.

The key file

Ownership is proven with a plain text file at https://yourhost/{key}.txt containing nothing but the key. The component handles the lifecycle: generateKey() mints a spec-compliant key, writeKeyFile() writes the file to your webroot, and verifyKeyFile() fetches it over HTTP and confirms the content matches before you submit anything.

You don't need to store the key anywhere. I derive it from the hostname so every site gets a stable key with zero configuration:

indexNowKey = "indexnow-" & lcase( hash( cgi.server_name ) );

My scheduled task calls verifyKeyFile() first and regenerates the file if it's missing or wrong. It costs one HTTP request.

Only submit what changed

The spec asks you not to resubmit unchanged URLs, so you need to track state somewhere. I added a nullable IndexNowDate column to the content table. Inserts and edits clear the column; a scheduled task submits whatever is pending and stamps the date on success:


qry = queryExecute( "SELECT ID, 'https://#websiteHost#' + Permalink AS Permalink
FROM Posts WHERE IndexNowDate IS NULL", {}, {} );
batchResult = indexNow.submitUrls( urls=valueArray( qry, "Permalink" ) );
if ( batchResult.success ) {
queryExecute( "UPDATE Posts SET IndexNowDate = SYSDATETIME() WHERE ID IN (:ids)",
{ "ids": { "value": valueList( qry.ID ), "list": true, "cfsqltype": "cf_sql_integer" } }, {} );
}

Two behaviors worth knowing. Deleted pages should be resubmitted, not skipped: the spiders re-fetch the URL, see the 404 or 410, and drop it from the index. And when a URL gets a 301/302 redirect, submit the old URL too so the engines learn about the move.

A ColdFusion 2016 workaround

An IndexNow key is allowed to be all digits, and that's how I learned something I hadn't run into before. On ColdFusion 2016, serializeJSON() converts a numeric-looking string into a JSON number even when it's wrapped in toString() or javacast("string", ...). A key of "12345678" goes over the wire as "key":12345678 and the API rejects the submission with a 403. CF2016 is the only platform that does this. ColdFusion 2018+, Lucee, and BoxLang all keep the string quoted. (I verified this on ACF 2016.0.17 by asserting against the raw request body; if you round-trip through deserializeJSON() to test it, the coercion is invisible.)

The fix is Nathan Mische's JSONUtil, which serializes values by their actual underlying Java type. Rather than making it a hard dependency, it's a constructor option:

indexNow = new IndexNow( host="www.example.com", key="12345678", useJSONUtil=true );

The default uses native serializeJSON(), which behaves correctly on every current engine. I enable useJSONUtil anyway. It's a little slower, but JSONUtil is stricter, which I like: deserializeJSON( JSONvar=body, strictMapping=true ) throws when the JSON contains duplicate keys (BoxLang behaves the same way, while Adobe ColdFusion silently accepts them), and it avoids another long-standing Adobe frustration when debugging or transforming data: every ACF version re-orders keys alphabetically when deserializing, while Lucee and BoxLang honor the original key order.

Testing

The repo includes a framework-free test harness: 38 tests that run against a local mock endpoint, so the suite never contacts the real API. It's verified green on Adobe ColdFusion 2016 and Lucee 5.4. There's also a demo page that exercises the whole flow against the mock, plus a localhost-only smoke test page for a one-time check against the live endpoint with a real key.

Grab it at github.com/JamoCA/cf-indexnow. If you hit an engine quirk I missed, open an issue and let me know.

21 Frontend System Design Concepts for Software Engineers

Mike's Notes

An excellent list of frontend system options.

Pipi generates a static frontend as a thin wrapper.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > System Design
  • Home > Handbook > 

Last Updated

28/12/2025

21 Frontend System Design Concepts for Software Engineers

By: Neo Kim & Shefali Jangid
System Design: 11/11/2025

Neo: I Teach You System Design

Shefali: I write about CSS, JavaScript, web dev resources, and solo projects.

If you’re coming from the backend, you probably think the frontend is just “HTML, CSS, maybe some JavaScript.” But honestly? Modern frontend engineering has grown into something much closer to backend system design.

Just like your APIs need to be fast, scalable, and reliable, frontend apps also have to handle millions of users, load content quickly, and stay observable and secure.

This newsletter is a quick introduction to frontend system design.

We’ll take concepts you already know from the backend, like caching, deployment pipelines, observability, and security, and see how they apply in the browser.

By the end, you’ll see that the frontend isn’t just about buttons and forms. It’s about building systems that run right in the user’s browser.

Onward.

I want to introduce Shefali Jangid as a guest author.

She’s a web developer, technical writer, and content creator with a love for frontend architecture and building things that scale.

Check out her work and socials:

  • Shefali.dev
  • GitHub
  • Twitter

You’ll often find her writing about web development, sharing UI tips, and building tools that make developers’ lives easier.

Rendering & Delivery Models

One of the first things to understand is how webpages reach your users.

The way you build and load them affects how fast, reliable, and smooth your site feels. You can pre-build pages, render them on the server, build them in the browser, or mix these approaches.

Building web pages works much like a server handles API responses. The trade-offs change depending on when and where the HTML gets generated.

Let’s start with pre-built pages and move to fully dynamic ones. We’ll see how each affects speed, scalability, and content freshness.

1 Static Site Generation (SSG)

Before SSG, websites worked in two fundamental ways. The server either built the page for every request, or the browser built it on the client side. That means:

  • Every request needed work to generate the page.
  • Pages could get slow if many people visit at once.
  • Caching was tricky, so scaling was hard.

SSG solves this by pre-building the HTML when you deploy your site. The system can fetch data during the build process, even for pages with dynamic content, which means all content is baked into static HTML files before any user visits them.

During the build process, the framework executes data-fetching code, queries your database, and generates complete HTML files for each route. The framework then uploads them to your CDN or hosting provider.

When users request a page, they receive a fully formed HTML document immediately, without waiting for server-side processing or client-side data fetching.

This makes SSG super fast for users because there’s no rendering delay. The trade-off is that if your content changes, you’ll need to rebuild and redeploy to update the static files, which is why SSG works best for content that doesn’t change frequently.

It’s like preparing API responses in advance; the hard work is done before anyone asks.

Why it matters:

  • Pages load super fast.
  • Easy to handle millions of users.
  • SEO is better because pages get fully rendered from the start.

Use case:

Documentation sites, marketing landing pages, or personal blogs where content updates happen through deployments, not user actions.

2 Incremental Static Regeneration (ISR)

Static Site Generation (SSG) is fast, but what if your content changes frequently? Rebuilding the whole site every time would be a pain.

That’s where Incremental Static Regeneration (ISR) comes in.

Pages are still pre-built, but they can update automatically without a full redeploy.

You just set a revalidation time; after that period, the next visitor triggers a background rebuild of that specific page on the server, not a full deployment. The old version loads instantly, so users don’t wait. After regeneration, the new version replaces the cached one. This occurs per page, not site-wide, allowing you to set different revalidation intervals for individual pages.

It’s like cached API responses with an expiry timer; users might glimpse an older version until it’s refreshed, but the update happens quietly behind the scenes.

Why it matters:

  • Just as fast as SSG, but the content stays fresh.
  • Perfect for dynamic websites like blogs or e-commerce sites.
  • Works with CDNs, so updates happen without downtime.

Use case:

E-commerce product pages where most content (e.g., descriptions or images) is static, but some parts, like prices or stock info, update occasionally.

ISR keeps the page fresh without full redeploys, while real-time data, such as live prices, can come from APIs.

3 Server-Side Rendering (SSR)

Server-Side Rendering (SSR) works the other way around: the server builds the page for each request. It fetches the data, generates the HTML, and sends it to the user.

Unlike Static Site Generation (SSG), which is great for mostly static pages, SSR is useful when content needs to stay fresh or personalised. For example, dashboards, user profiles, or live feeds. Because the system generates pages in real time, they always display the latest data instead of relying on pre-built files.

Think of it like a regular API endpoint; everything gets computed on demand.

Why it matters:

  • Keeps content fresh and easy to personalise.
  • Perfect for pages that need real-time data or personalised content.
Note: Under heavy traffic, SSR can slow down because it builds each page on demand, but caching can help balance load and speed.

Use case:

Social media feeds, admin dashboards, or user-specific pages where content varies by session.

4 Client-Side Rendering (CSR)

CSR means the browser does most of the work instead of the server. The server sends only a basic HTML page and some JavaScript. The browser then loads the data and builds the page on the fly.

This approach is useful when you need rich interactivity, real-time updates, or pages that change often based on user actions - things that static or server-rendered pages can’t handle easily.

Think of it like sending raw JSON and letting the client put it together.

Note: The first page load can be slower because the browser needs to download and run JavaScript before showing the content. Since pages get built in the browser, search engines might not see them immediately, so you might need extra setup like pre-rendering or server-side rendering for better SEO.

Why it matters:

  • Reduces pressure on the server.
  • Makes the app more interactive and responsive.
  • Works best for apps people use for a long time, like dashboards or editors.

Use case:

Complex apps like Figma, Notion, or Google Docs, where the app is highly interactive and users stay on the page for extended sessions.

5 Hybrid Rendering

Sometimes, one approach just isn’t enough.

Different parts of your app might have different needs. For example, some pages stay mostly the same, while others need fresh or personalised data. That’s where hybrid rendering comes in.

It mixes different strategies:

  • Server-side rendering (SSR) for pages that need live or personalised content,
  • Static site generation (SSG) for pages that rarely change,
  • And client-side rendering (CSR) for sections with lots of interactivity.

Think of it like combining pre-computed API responses with on-demand endpoints - all in the same system.

Why it matters:

  • You get the best of everything: speed, fresh content, and interactivity.
  • Allows you to choose the right approach for each page or component.
  • Reduces overloading the server while keeping content dynamic where needed.

Use case:

Large-scale apps like e-commerce platforms often combine different rendering strategies:

  • The homepage and category pages use static generation for speed.
  • Product pages use incremental static regeneration to keep content fresh.
  • User account pages use server-side rendering for personalised data.
  • The shopping cart uses client-side rendering for real-time updates without page reloads.

6 Content Delivery Networks (CDNs) & Edge Delivery

No matter which rendering method you choose, serving content efficiently is super important. CDNs keep copies of your static files on servers worldwide. This lets users download them from a nearby location instead of your main server.

This is especially useful for global audiences. For example, when someone in India visits a site hosted in the US, the CDN delivers the content from a local server, making it load much faster.

Edge rendering takes this idea a step further. Instead of just serving static files, it can actually run code or build pages at the edge, closer to the user, which reduces latency even more.

Think of it like having caches and compute nodes near your users, so requests go to a nearby server instead of your main database.

Why it matters:

  • Faster load times everywhere.
  • Easy to scale to millions of users.
  • Works perfectly with SSG, ISR, SSR, or hybrid setups.

Use case:

Any globally distributed application. Media sites like The New York Times use CDNs to serve articles instantly worldwide.

Performance & Optimisation

Now that you understand how your pages get rendered, the next obvious question is, “How quickly do they actually load?”

Even the most beautiful app can be frustrating if it takes too long to open or lags while being used. In frontend system design, speed really matters.

Let’s dive in!

7 Web Performance Metrics

To really understand your app’s speed, there are a few key metrics you should watch closely:

  • TTFB (Time to First Byte): The time it takes for your browser to get the first piece of data back from the server or CDN after making a request.
  • FCP (First Contentful Paint): The moment when something first appears on the screen, like text, an image, or a button, so the user knows the page is loading.
  • LCP (Largest Contentful Paint): The time it takes for the main part of the page, like a large image or headline, to fully appear on the screen.
  • CLS (Cumulative Layout Shift): It measures how much the page layout jumps around while loading, like when text or buttons suddenly shift because images or ads are still loading.

These are basically the frontend versions of response time, throughput, and latency in backend systems. It’s important to keep a close eye on them; users can notice even minor delays of a few hundred milliseconds.

Why it matters:

  • You can spot slow pages before users even notice.
  • Improves engagement and reduces bounce rates.
  • Helps guide your optimisations for a smoother experience.

Use case:

E-commerce sites must optimise for LCP (product images) and CLS (avoid layout shifts during checkout). News sites focus on FCP to show headlines quickly.

8 Lazy Loading

Of course, fast pages aren’t just about metrics; they’re also about smart resource management.

Not everything on a page needs to load immediately. Lazy loading means loading heavy assets, like images, videos, or big components, only when they’re actually needed.

This works by using techniques like the Intersection Observer API or conditional imports, which tell the browser to fetch those resources only when they come into view or are triggered by user interaction.

It’s like fetching extra data from an API only when the user asks for it.

Why it matters:

  • Cuts down the initial load time.
  • Makes the pages feel faster and smoother.
  • Saves bandwidth for users who don’t need everything immediately.

Use case:

Image-heavy sites like Pinterest or Instagram use lazy loading extensively; images below the fold don’t load until you scroll.

9 Service Workers & Caching

Once you’ve optimised loading, you can make your app faster and more reliable using service workers and caching.

Service workers are background scripts that run in a separate thread from your main web page. They can intercept network requests and cache important files or data, helping your app load faster and even work offline.

Think of them as a smart middle layer between the browser and the network; if something is already cached, it’s served instantly instead of being fetched again.

Why it matters:

  • Speeds up repeat visits.
  • Reduces the load on servers.
  • Keeps apps usable even with poor or no internet connection.

Use case:

Progressive Web Apps like Twitter Lite or Starbucks PWA, which cache core UI and recent content, so users can browse even on unstable mobile networks.

Data & State Management

Once your UI loads quickly, the next step is to think about the data behind it.

In real apps, this data (also called state) can come from different places:

  • Some live inside a single component (a reusable piece of the UI, like a button),
  • Some are shared across the app,
  • And others come from APIs.

How you manage this state can make or break your app’s speed, reliability, and scalability.

10 State Management (Local, Global, Server Cache)

  • Local state: data that lives inside a single component, used for things like toggles, forms, or small interactions. It’s simple to manage and doesn’t add much complexity.
  • Global state: data that’s shared across multiple components or pages, like user info or theme settings. Tools like Redux, Zustand, or React Context help manage it.
  • Server cache: stores frequently used API data on the client so the app doesn’t have to fetch it again and again, making it faster and reducing server load.

Think of it like database caching: by deciding where data should live, you can make your app more responsive, reliable, and easier to scale.

Why it matters:

  • Keeps your app responsive.
  • Reduces unnecessary API calls.
  • Makes scaling smoother as your app grows.

Use case:

Local state for a modal’s open/closed status. Global state for theme preference (dark mode) that affects every component. Server-side cache for user profile data displayed by multiple components.

11 API Caching with Expiration

Caching doesn’t stop at the component level. You can store API responses in memory, IndexedDB (a browser database for larger data), or localStorage (for smaller key-value data), and set expiration rules to make sure data stays fresh.

It’s like having a Redis cache server, but right in the browser instead of on your server.

Why it matters:

  • Keeps data up-to-date for users.
  • Reduces repeated server requests.
  • Makes your app feel faster.

Use case:

A news app might cache articles for a few minutes so users can read offline, while comments refresh more often to stay up to date. Similarly, a SaaS dashboard could cache chart data while the user is on the page, then refresh it when they come back later.

12 GraphQL vs REST (Reducing Over/Under-Fetching)

How you fetch data also affects performance.

  • REST: Can sometimes send too much data or not enough, making your app fetch extra information or require additional requests.
  • GraphQL: A query language for APIs that lets the client ask for exactly the data it needs, avoiding extra or missing information. This avoids over-fetching or under-fetching data and helps reduce unnecessary requests.

It’s like how you optimise database queries on the backend to make them faster and use less bandwidth, but this happens on the frontend.

GraphQL sits between the client and the server as one endpoint. The client asks for exactly the data it needs, and the server’s GraphQL layer collects that data from databases or other APIs, then sends back a clean, organised response.

This way, you make one flexible request instead of several REST calls, making it faster and more data-efficient.

Why it matters:

  • Saves bandwidth, especially on mobile networks.
  • Reduces unnecessary requests.
  • Simplifies client-side data handling.

Use case:

GraphQL works best for complex apps that need data from many places at once, like GitHub. One GraphQL query can get a pull request, comments, and author info in a single request instead of several REST calls. While REST is simpler and great for apps with stable data, like blogs or public APIs that rely on caching.

13 Pagination Strategies (Cursor vs Offset)

Loading large lists or tables all at once can be heavy. Pagination helps break the data into manageable chunks.

  • Offset pagination: Uses page numbers or record counts (like ?page=2 or ?offset=20) to fetch data. It’s simple and works well for lists that don’t change often. But the list order shifts if new items are added or old ones are removed. This can make the same offset return different items, leading to duplicates or missing entries.
  • Cursor pagination: Uses a pointer to mark where the last item ended, so the next request starts right after it. It’s more reliable for live or frequently updated data (social feeds or chat messages) because it keeps track of the exact position in the dataset. That means even if new items are added or removed while you’re scrolling, you won’t see duplicates or miss entries.

Why it matters:

  • Handles large datasets efficiently.
  • Prevents slowdowns and performance bottlenecks.
  • Keeps dynamic lists reliable and consistent.

Use case:

  • Offset pagination: best for data tables with stable data and clear page numbers, such as admin panels or product catalogs.
  • Cursor pagination: ideal for infinite scroll feeds like social media timelines, notification lists, or any real-time list where items are frequently added or removed.

14 Real-Time Data & Networking (WebSockets, SSE, Polling)

Finally, some apps need live updates, like chat apps, dashboards, or notifications. How you handle real-time data matters.

  • WebSockets: Let the client and server send messages to each other in real time, both ways, without constantly asking for updates.
  • Server-Sent Events (SSE): The server can push updates to the client in real time, but communication only goes one way, from server to client.
  • Polling: The client regularly asks the server for updates. It’s simple to set up, but it can put more load on the server.

It’s like building event-driven systems on the backend, but here it happens in the browser.

Why it matters:

  • Supports live dashboards, chat, and notifications.
  • Improves interactivity and user engagement.
  • Allows you to choose the right strategy for your app’s needs.

Use case:

  • WebSockets: chat apps (Slack), multiplayer games, collaborative editing (Google Docs).
  • SSE: live notifications, stock tickers, server logs streaming to a dashboard.
  • Polling: simple use cases like checking for new emails or status updates.

Architecture & Scalability

As your app grows, managing complexity becomes just as important as writing features. Frontend architecture isn’t just about code; it’s about building systems that are maintainable, scalable, and predictable.

15 Micro Frontends

When multiple teams work on the same app, things can get messy fast.

Micro frontends let each team build and deploy their part of the app separately. For example, one team handles the dashboard while another builds the settings page. Technically, the app is divided into smaller frontend projects that are combined at runtime to work as one seamless app.

A module federation feature (for example, in tools like Webpack) lets these separate projects share code (like components or utilities) directly in the browser, without rebuilding or duplicating code across projects.

Why it matters:

  • Teams can develop features faster and in parallel.
  • Reduces duplicated code across bundles.
  • Supports independent deployment cycles, so updates don’t block each other.

Use case:

Large enterprises with multiple teams working on different product areas. For example, big companies like Zalando, IKEA, DAZN, and Spotify use micro-frontends so each team can build and release their part of the app on their own.

16 Component-Based Architecture & Design Systems

Components are the building blocks of your app. A design system ensures these components stay consistent and reusable across teams and projects.

It’s like having reusable backend modules or libraries, but for your UI.

Why it matters:

  • Makes the UI predictable and easier to maintain.
  • Encourages code reuse across pages and projects.
  • Helps teams scale efficiently without creating chaos.

Use case:

  • Used by companies with many products or teams to keep design consistent, like Shopify’s Polaris or IBM’s Carbon, which are open-source design systems containing ready-to-use UI components, styles, and guidelines.
  • Even small startups benefit: a shared set of 10–20 components (like buttons and modals) helps teams build faster and keep the UI consistent.

17 Build & Deployment Pipelines (CI/CD for Frontend)

Frontend apps also benefit from CI/CD (Continuous Integration and Continuous Deployment) pipelines, just like backend services. These pipelines automatically handle steps like building the app, running tests, and deploying updates.

In simple terms, every time you push code, CI/CD tools check that nothing breaks and then safely release the latest version, making deployments faster, more reliable, and less manual.

Why it matters:

  • Minimises human errors during deployment.
  • Enables fast, reliable releases.
  • Makes scaling and frequent updates much smoother.

Use case:

Works for any app with regular updates, from small teams auto-deploying to Vercel to big companies like Netflix releasing thousands of times a day. It keeps updates fast, safe, and reliable.

User Experience & Reliability

Your users don’t care about your architectures or caching strategies; they just want the app to be fast, reliable, and easy to use.

18 Accessibility (a11y) & Mobile-First Design

Accessibility and mobile-first design aren’t just design principles; they’re system-level considerations. Accessibility ensures your app’s UI and code structure work for everyone, including people using assistive technologies.

Mobile-first design forces you to build efficient layouts, load lighter assets, and prioritize key features, all of which influence performance, scalability, and overall frontend architecture.

Why it matters:

  • Reaches more users.
  • Makes your app easier and more pleasant to use.
  • Ensures a consistent experience across devices.

Use case:

Government sites (accessibility is legally required in many countries), e-commerce, and content platforms. Mobile-first is essential for apps in developing markets where mobile is the main or only device.

19 Progressive Web Apps (PWAs) & Offline-First

Progressive Web Apps (PWAs) are web apps that behave like native apps. They can work offline, send notifications, and even be installed on a device.

They use a few key technologies:

  • Service workers run in the background to cache important files like HTML, CSS, and API responses.
  • A web app manifest defines how the app looks and behaves when installed.
  • And HTTPS keeps everything secure.

Together, these make the app fast, reliable, and installable.

Why it matters:

  • Users can access your app anywhere.
  • Reduces load on servers.
  • Improves reliability and user trust.

Use case:

Apps where offline access is valuable: Twitter Lite, Starbucks PWA, field service apps, and news apps.

20 Security Basics (XSS, CSRF, CSP, Authentication)

Speed means nothing without security. Frontend isn’t just about the UI; it’s also the first line of defence for your app.

  • XSS (Cross-Site Scripting): Stop attackers from injecting malicious scripts into your app.
  • CSRF (Cross-Site Request Forgery): Protect your forms and actions that change data from being triggered by attackers without the user’s consent.
  • CSP (Content Security Policy): A rule set that helps prevent malicious scripts from running in your app.
  • Authentication: Make sure user tokens and sessions are stored and handled securely in the browser.

Why it matters:

  • Protects your users and their data.
  • Prevents common attacks before they reach the backend.
  • Builds trust and helps with compliance.

Use case:

Any app handling sensitive data. Financial apps need strict CSP and token handling. Social platforms must prevent XSS to avoid account takeovers. E-commerce sites need CSRF protection on checkout to prevent unauthorised purchases.

21 Observability & Error Monitoring (Client-Side)

Even if everything works well, things can still break in production. That’s why observability is important.

Frontend errors are just like 500 errors in your backend; they happen. Monitoring tools like Sentry or LogRocket help you track:

  • JS exceptions: errors that happen in your JavaScript code while the app is running.
  • Performance bottlenecks: parts of your app that slow it down or make it lag.
  • User interactions leading to errors: actions by users that trigger bugs or crashes in your app.

These tools add a small script to your app. When something breaks, it collects information like the error message, what the user was doing, and browser details. Then it sends that data to the tool’s server, where you can see and fix the issue from your dashboard.

Why it matters:

  • Detects and resolves issues faster.
  • Keeps your app stable and performant.
  • Improves the overall user experience and trust.

Use case:

Used in production apps with real users. SaaS teams track errors right after deployment, e-commerce sites watch checkout issues, and session replay tools help support teams see what confused users without extra bug reports.

Conclusion

Frontend system design is basically backend system design, just happening in the user’s browser.

Every choice you make, like rendering method, caching strategy, state management, architecture, and security, affects speed, scalability, and reliability.

So next time you’re building a frontend, ask yourself:

  • Where should computation happen? On the server, in the client’s browser, or at the edge?
  • When does the data need to be up-to-date? Prebuilt, cached, or real-time?
  • How can we keep the app fast and reliable? Lazy loading, smart caching, or micro frontends?
  • How do we scale this? Can the architecture handle 10x traffic? 100x?
  • How do we maintain this? Will new developers understand the architecture? Can teams work independently?

Think of your frontend as a distributed system. Treat it that way, and your users will get an app that’s fast, smooth, and seamless, exactly what they expect.

👋 I’d like to thank Shefali for writing this newsletter!

Plus, don’t forget to check out her work and socials:

  • Shefali.dev
  • GitHub
  • Twitter

You’ll often find her writing about web development, sharing UI tips, and building tools that make developers’ lives easier.