Use w3m for archiving

This commit is contained in:
David Eisinger
2024-01-17 12:04:56 -05:00
parent c5f0c6161a
commit ae64f3eb0a
80 changed files with 28830 additions and 29811 deletions

View File

@@ -1,288 +1,267 @@
#[1]macwright.com - Micro [2]macwright.com - Micro
Tom MacWright
tom@macwright.com
Tom MacWright
tom@macwright.com
Tom MacWright
* [3]Writingâ‡
* [4]Reading
* [5]Photos
* [6]Projects
* [7]Drawings
* [8]Micro
* [9]About
• [1]Writing⇠
• [2]Reading
• [3]Photos
• [4]Projects
• [5]Drawings
• [6]Micro
• [7]About
Second-guessing the modern web
The emerging norm for web development is to build a React single-page
application, with server rendering. The two key elements of this
architecture are something like:
1. The main UI is built & updated in JavaScript using React or
something similar.
2. The backend is an API that that application makes requests against.
The emerging norm for web development is to build a React single-page
application, with server rendering. The two key elements of this architecture
are something like:
This idea has really swept the internet. It started with a few major
popular websites and has crept into corners like marketing sites and
blogs.
1. The main UI is built & updated in JavaScript using React or something
similar.
2. The backend is an API that that application makes requests against.
I’m increasingly skeptical of it.
This idea has really swept the internet. It started with a few major popular
websites and has crept into corners like marketing sites and blogs.
There is a sweet spot of React: in moderately interactive interfaces.
Complex forms that require immediate feedback, UIs that need to move
around and react instantly. That’s where it excels. I helped build the
editors in [10]Mapbox Studio and [11]Observable and for the most part,
React was a great choice.
Im increasingly skeptical of it.
But there’s a lot on either side of that sweet spot.
There is a sweet spot of React: in moderately interactive interfaces. Complex
forms that require immediate feedback, UIs that need to move around and react
instantly. Thats where it excels. I helped build the editors in [8]Mapbox
Studio and [9]Observable and for the most part, React was a great choice.
The high performance parts aren’t React. [12]Mapbox GL, for example, is
vanilla JavaScript and probably should be forever. The level of
abstraction that React works on is too high, and the cost of using
React - in payload, parse time, and so on - is too much for any company
to include it as part of an SDK. Same with the [13]Observable runtime,
the juicy center of that product: it’s very performance-intensive and
would barely benefit from a port.
But theres a lot on either side of that sweet spot.
The less interactive parts don’t benefit much from React. Listing
pages, static pages, blogs - these things are increasingly built in
React, but the benefits they accrue are extremely narrow. A lot of the
optimizations we’re deploying to speed up these things, things like
bundle splitting, server-side rendering, and prerendering, are
triangulating what we had before the rise of React.
The high performance parts arent React. [10]Mapbox GL, for example, is vanilla
JavaScript and probably should be forever. The level of abstraction that React
works on is too high, and the cost of using React - in payload, parse time, and
so on - is too much for any company to include it as part of an SDK. Same with
the [11]Observable runtime, the juicy center of that product: its very
performance-intensive and would barely benefit from a port.
And they’re kind of messy optimizations. Here are some examples.
The less interactive parts dont benefit much from React. Listing pages, static
pages, blogs - these things are increasingly built in React, but the benefits
they accrue are extremely narrow. A lot of the optimizations were deploying to
speed up these things, things like bundle splitting, server-side rendering, and
prerendering, are triangulating what we had before the rise of React.
And theyre kind of messy optimizations. Here are some examples.
Bundle splitting.
As your React application grows, the application bundle grows. Unlike
with a traditional multi-page app, that growth affects every visitor:
you download the whole app the first time that you visit it. At some
point, this becomes a real problem. Someone who lands on the About page
is also downloading 20 other pages in the same application bundle.
Bundle splitting ‘solves’ this problem by creating many JavaScript
bundles that can lazily load each other. So you load the About page and
what your browser downloads is an ‘index’ bundle, and then that ‘index’
bundle loads the ‘about page’ bundle.
As your React application grows, the application bundle grows. Unlike with a
traditional multi-page app, that growth affects every visitor: you download the
whole app the first time that you visit it. At some point, this becomes a real
problem. Someone who lands on the About page is also downloading 20 other pages
in the same application bundle. Bundle splitting solves this problem by
creating many JavaScript bundles that can lazily load each other. So you load
the About page and what your browser downloads is an index bundle, and then
that index bundle loads the about page bundle.
This sort of solves the problem, but it’s not great. Most bundle
splitting techniques require you to load that ‘index bundle’, and then
only once that JavaScript is loaded and executed does your browser know
which ‘page bundle’ it needs. So you need two round-trips to start
rendering.
This sort of solves the problem, but its not great. Most bundle splitting
techniques require you to load that index bundle, and then only once that
JavaScript is loaded and executed does your browser know which page bundle it
needs. So you need two round-trips to start rendering.
And then there’s the question of updating code-split bundles. User
sessions are surprisingly long: someone might have your website open in
a tab for weeks at a time. I’ve seen it happen. So if they open the
‘about page’, keep the tab open for a week, and then request the ‘home
page’, then the home page that they request is dictated by the index
bundle that they downloaded last week. This is a deeply weird and
under-discussed situation. There are essentially two solutions to it:
1. You keep all generated JavaScript around, forever, and people will
see the version of the site that was live at the time of their
first page request.
2. You create a system that alerts users when you’ve deployed a new
version of the site, and prompt them to reload.
And then theres the question of updating code-split bundles. User sessions are
surprisingly long: someone might have your website open in a tab for weeks at a
time. Ive seen it happen. So if they open the about page, keep the tab open
for a week, and then request the home page, then the home page that they
request is dictated by the index bundle that they downloaded last week. This is
a deeply weird and under-discussed situation. There are essentially two
solutions to it:
The first solution has a drawback that might not be immediately
obvious. In those intervening weeks between loading the site and
clicking a link, you might’ve deployed a new API version. So the user
will be using an old version of your JavaScript frontend with a new
version of your API backend, and they’ll trigger errors that none of
your testing knows about, because you’ll usually be testing current
versions of each.
1. You keep all generated JavaScript around, forever, and people will see the
version of the site that was live at the time of their first page request.
2. You create a system that alerts users when youve deployed a new version of
the site, and prompt them to reload.
And the second solution, while it works (and is what we implemented for
Mapbox Studio), is a bizarre way for a web application to behave.
Prompting users to ‘update’ is something from the bad old days of
desktop software, not from the shiny new days of the web.
The first solution has a drawback that might not be immediately obvious. In
those intervening weeks between loading the site and clicking a link, you
mightve deployed a new API version. So the user will be using an old version
of your JavaScript frontend with a new version of your API backend, and theyll
trigger errors that none of your testing knows about, because youll usually be
testing current versions of each.
Sure: traditional non-SPA websites are not immune to this pitfall.
Someone might load your website, have a form open for many weeks, and
then submit it after their session expired or the API changed. But
that’s a much more limited exposure to failure than in the SPA case.
And the second solution, while it works (and is what we implemented for Mapbox
Studio), is a bizarre way for a web application to behave. Prompting users to
update is something from the bad old days of desktop software, not from the
shiny new days of the web.
Sure: traditional non-SPA websites are not immune to this pitfall. Someone
might load your website, have a form open for many weeks, and then submit it
after their session expired or the API changed. But thats a much more limited
exposure to failure than in the SPA case.
Server-Side Rendering
Okay, so the theory here is that SPAs are initially a blank page, which
is then filled out by React & JavaScript. That’s bad for performance:
HTML pages don’t need to be blank initially. So, Server-Side Rendering
runs your JavaScript frontend code on the backend, creating a
filled-out HTML page. The user loads the page, which now has
pre-rendered content, and then the JavaScript loads and makes the page
interactive.
Okay, so the theory here is that SPAs are initially a blank page, which is then
filled out by React & JavaScript. Thats bad for performance: HTML pages dont
need to be blank initially. So, Server-Side Rendering runs your JavaScript
frontend code on the backend, creating a filled-out HTML page. The user loads
the page, which now has pre-rendered content, and then the JavaScript loads and
makes the page interactive.
A great optimization, but again, caveats.
A great optimization, but again, caveats.
The first is that the page you initially render is dead: you’ve created
the [14]Time To Interactive metric. It’s your startup’s homepage, and
it has a “Sign upâ€<EFBFBD> button, but until the JavaScript loads, that button
doesn’t do anything. So you need to compensate. Either you omit some
interactive elements on load, or you try really hard to make sure that
the JavaScript loads faster than users will click, or you make some
elements not require JavaScript to work - like making them normal links
or forms. Or some combination of those.
The first is that the page you initially render is dead: youve created the
[12]Time To Interactive metric. Its your startups homepage, and it has a
Sign up button, but until the JavaScript loads, that button doesnt do
anything. So you need to compensate. Either you omit some interactive elements
on load, or you try really hard to make sure that the JavaScript loads faster
than users will click, or you make some elements not require JavaScript to work
- like making them normal links or forms. Or some combination of those.
And then there’s the authentication story. If you do SSR on any pages
that are custom to the user, then you need to forward any cookies or
authentication-relevant information to your API backend and make sure
that you never cache the server-rendered result. Your
formerly-lightweight application server is now doing quite a bit of
labor, running React & making API requests in order to do this
pre-rendering.
And then theres the authentication story. If you do SSR on any pages that are
custom to the user, then you need to forward any cookies or
authentication-relevant information to your API backend and make sure that you
never cache the server-rendered result. Your formerly-lightweight application
server is now doing quite a bit of labor, running React & making API requests
in order to do this pre-rendering.
APIs
The dream of APIs is that you have generic, flexible endpoints upon
which you can build any web application. That idea breaks down pretty
fast.
The dream of APIs is that you have generic, flexible endpoints upon which you
can build any web application. That idea breaks down pretty fast.
Most interactive web applications start to triangulate on “one query
per page.â€<C3A2> API calls being generic or reusable never seems to persist
as a value in infrastructure. This is because a large portion of web
applications are, at their core, query & transformation interfaces on
top of databases. The hardest performance problems they tend to have
are query problems and transfer problems.
Most interactive web applications start to triangulate on one query per page.”
API calls being generic or reusable never seems to persist as a value in
infrastructure. This is because a large portion of web applications are, at
their core, query & transformation interfaces on top of databases. The hardest
performance problems they tend to have are query problems and transfer
problems.
For example: a generically-designed REST API that tries not to mix
‘concerns’ will produce a frontend application that has to make lots of
requests to display a page. And then a new-age GraphQL application will
suffer under the [15]N+1 query problem at the database level until an
optimization arrives. And a traditional “make a query and put it on a
pageâ€<C3A2> application will just, well, try to write some good queries.
For example: a generically-designed REST API that tries not to mix concerns
will produce a frontend application that has to make lots of requests to
display a page. And then a new-age GraphQL application will suffer under the
[13]N+1 query problem at the database level until an optimization arrives. And
a traditional make a query and put it on a page” application will just, well,
try to write some good queries.
None of these solutions are silver bullets: I’ve worked with
overly-strict REST APIs, optimization-hungry GraphQL APIs, and
hand-crafted SQL APIs. But no option really lets a web app be careless
about its data-fetching layer. Web applications can’t sit on top of
independently-designed APIs: to have a chance at performance, the
application and its datasource need to be designed as one.
None of these solutions are silver bullets: Ive worked with overly-strict REST
APIs, optimization-hungry GraphQL APIs, and hand-crafted SQL APIs. But no
option really lets a web app be careless about its data-fetching layer. Web
applications cant sit on top of independently-designed APIs: to have a chance
at performance, the application and its datasource need to be designed as one.
Data fetching
Speaking of data fetching. It’s really important and really bizarre in
React land. Years ago, I expected that some good patterns would emerge.
Frankly, they didn’t.
Speaking of data fetching. Its really important and really bizarre in React
land. Years ago, I expected that some good patterns would emerge. Frankly, they
didnt.
There are decent patterns in the form of GraphQL, but for a React
component that loads data with fetch from an API, the solutions have
only gotten weirder. There’s great documentation for everything else,
but old-fashioned data loading is relegated to one example of how to
mock out ‘fetch’ for testing, and lots of Medium posts of varying
quality.
__________________________________________________________________
There are decent patterns in the form of GraphQL, but for a React component
that loads data with fetch from an API, the solutions have only gotten weirder.
Theres great documentation for everything else, but old-fashioned data loading
is relegated to one example of how to mock out fetch for testing, and lots of
Medium posts of varying quality.
Don’t read this as anti-React. I still think React is pretty great, and
for a particular set of use cases it’s the best tool you can find. And
I explicitly want to say that – from what I’ve seen – most other
Single-Page-Application tools share most of these problems. They’re
issues with the pattern, not the specific frameworks used to implement
it. React alternatives have some great ideas, and they might be better,
but they are ultimately really similar.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
But I’m at the point where I look at where the field is and what the
alternative patterns are – taking a second look at unloved, unpopular,
uncool things like Django, Rails, Laravel – and think what the heck is
happening. We’re layering optimizations upon optimizations in order to
get the SPA-like pattern to fit every use case, and I’m not sure that
it is, well, worth it.
Dont read this as anti-React. I still think React is pretty great, and for a
particular set of use cases its the best tool you can find. And I explicitly
want to say that  from what Ive seen  most other Single-Page-Application
tools share most of these problems. Theyre issues with the pattern, not the
specific frameworks used to implement it. React alternatives have some great
ideas, and they might be better, but they are ultimately really similar.
And it should be easy to do a good job.
But Im at the point where I look at where the field is and what the
alternative patterns are  taking a second look at unloved, unpopular, uncool
things like Django, Rails, Laravel  and think what the heck is happening.
Were layering optimizations upon optimizations in order to get the SPA-like
pattern to fit every use case, and Im not sure that it is, well, worth it.
Frameworks should lure people into the [16]pit of success, where
following the normal rules and using normal techniques is the winning
approach.
And it should be easy to do a good job.
I don’t think that React, in this context, really is that pit of
success. A naïvely implemented React SPA isn’t stable, or efficient,
and it doesn’t naturally scale to significant complexity.
Frameworks should lure people into the [14]pit of success, where following the
normal rules and using normal techniques is the winning approach.
You can add optimizations on top of it that fix those problems, or you
can use a framework like Next.js that will include those optimizations
by default. That’ll help you get pretty far. But then you’ll be lured
by all of the easy one-click ways to add bloat and complexity. You’ll
be responsible for keeping some of these complex, finicky optimizations
working properly.
I dont think that React, in this context, really is that pit of success. A
naïvely implemented React SPA isnt stable, or efficient, and it doesnt
naturally scale to significant complexity.
And for what? Again - there is a swath of use cases which would be hard
without React and which aren’t complicated enough to push beyond
React’s limits. But there are also a lot of problems for which I can’t
see any concrete benefit to using React. Those are things like blogs,
shopping-cart-websites, mostly-[17]CRUD-and-forms-websites. For these
things, all of the fancy optimizations are trying to get you closer to
the performance you would’ve gotten if you just hadn’t used so much
technology.
You can add optimizations on top of it that fix those problems, or you can use
a framework like Next.js that will include those optimizations by default.
Thatll help you get pretty far. But then youll be lured by all of the easy
one-click ways to add bloat and complexity. Youll be responsible for keeping
some of these complex, finicky optimizations working properly.
I can, for example, guarantee that this blog is faster than any Gatsby
blog (and much love to the Gatsby team) because there is nothing that a
React static site can do that will make it faster than a non-React
static site.
__________________________________________________________________
And for what? Again - there is a swath of use cases which would be hard without
React and which arent complicated enough to push beyond Reacts limits. But
there are also a lot of problems for which I cant see any concrete benefit to
using React. Those are things like blogs, shopping-cart-websites, mostly-[15]
CRUD-and-forms-websites. For these things, all of the fancy optimizations are
trying to get you closer to the performance you wouldve gotten if you just
hadnt used so much technology.
But the cultural tides are strong. Building a company on Django in 2020
seems like the equivalent of driving a PT Cruiser and blasting Faith
Hill’s “Breatheâ€<C3A2> on a CD while your friends are listening to The Weeknd
in their Teslas. Swimming against this current isn’t easy, and not in a
trendy contrarian way.
I can, for example, guarantee that this blog is faster than any Gatsby blog
(and much love to the Gatsby team) because there is nothing that a React static
site can do that will make it faster than a non-React static site.
I don’t think that everyone’s using the SPA pattern for no reason. For
large corporations, it allows teams to work independently: the
“frontend engineersâ€<C3A2> can “consumeâ€<C3A2> “APIsâ€<C3A2> from teams that probably work
in a different language and can only communicate through the hierarchy.
For heavily interactive applications, it has real benefits in
modularity, performance, and structure. And it’s beneficial for
companies to shift computing requirements from their servers to their
customers browsers: a real win for reducing their spend on
infrastructure.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
But I think there are a lot of problems that are better solved some
other way. There’s no category winner like React as an alternative.
Ironically, backends are churning through technology even faster than
frontends, which have been loyal to one programming language for
decades. There are some age-old technologies like Rails, Django, and
Laravel, and there are a few halfhearted attempts to do templating and
“serve web pagesâ€<C3A2> from Go, Node, and other new languages. If you go
this way, you’re beset by the cognitive dissonance of following in the
footsteps of enormous projects - Wikipedia rendering web pages in PHP,
Craigslist rendering webpages in Perl - but being far outside the norms
of modern web development. If Wikipedia were started today, it’d be
React. Maybe?
But the cultural tides are strong. Building a company on Django in 2020 seems
like the equivalent of driving a PT Cruiser and blasting Faith Hills “Breathe”
on a CD while your friends are listening to The Weeknd in their Teslas.
Swimming against this current isnt easy, and not in a trendy contrarian way.
What if everyone’s wrong? We’ve been wrong before.
Follow-ups & commmentary
* [18]"In defense of the modern web", Rich Harris
* [19]Friday Night Deploys (Podcast) #22: A Brief Discussion On The
State Of The Modern Web
* [20]Frontend First (Podcast): Read & Discuss
* [21]A Ready-To-Try Concept in Response to “Second-guessing the
modern webâ€<C3A2>
I dont think that everyones using the SPA pattern for no reason. For large
corporations, it allows teams to work independently: the “frontend engineers”
can “consume” “APIs” from teams that probably work in a different language and
can only communicate through the hierarchy. For heavily interactive
applications, it has real benefits in modularity, performance, and structure.
And its beneficial for companies to shift computing requirements from their
servers to their customers browsers: a real win for reducing their spend on
infrastructure.
May 10, 2020 [22]Tom MacWright ([23]@tmcw, [24]@tmcw@mastodon.social)
But I think there are a lot of problems that are better solved some other way.
Theres no category winner like React as an alternative. Ironically, backends
are churning through technology even faster than frontends, which have been
loyal to one programming language for decades. There are some age-old
technologies like Rails, Django, and Laravel, and there are a few halfhearted
attempts to do templating and “serve web pages” from Go, Node, and other new
languages. If you go this way, youre beset by the cognitive dissonance of
following in the footsteps of enormous projects - Wikipedia rendering web pages
in PHP, Craigslist rendering webpages in Perl - but being far outside the norms
of modern web development. If Wikipedia were started today, itd be React.
Maybe?
References
What if everyones wrong? Weve been wrong before.
1. https://macwright.com/micro/rss.xml
2. https://macwright.com/micro/atom.xml
3. https://macwright.com/
4. https://macwright.com/reading/
5. https://macwright.com/photos/
6. https://macwright.com/projects/
7. https://macwright.com/drawings/
8. https://macwright.com/micro/
9. https://macwright.com/about/
10. https://www.mapbox.com/mapbox-studio/
11. https://observablehq.com/
12. https://docs.mapbox.com/mapbox-gl-js/api/
13. https://github.com/observablehq/runtime
14. https://web.dev/interactive/
15. https://engineering.shopify.com/blogs/engineering/solving-the-n-1-problem-for-graphql-through-batching
16. https://blog.codinghorror.com/falling-into-the-pit-of-success/
17. https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
18. https://dev.to/richharris/in-defense-of-the-modern-web-2nia
19. https://dev.to/devplebs/friday-night-deploys-22-a-brief-discussion-on-the-state-of-the-modern-web-2961
20. https://frontendfirst.fm/episodes/read-and-discuss-second-guessing-the-modern-web
21. https://medium.com/@kevinkirchner/a-ready-to-try-concept-in-response-to-second-guessing-the-modern-web-6946ec4d0598
22. https://macwright.com/about/
23. https://twitter.com/intent/follow?screen_name=tmcw&user_id=1458271
24. https://mastodon.social/@tmcw
Follow-ups & commmentary
• [16]"In defense of the modern web", Rich Harris
• [17]Friday Night Deploys (Podcast) #22: A Brief Discussion On The State Of
The Modern Web
• [18]Frontend First (Podcast): Read & Discuss
• [19]A Ready-To-Try Concept in Response to “Second-guessing the modern web”
May 10, 2020  [20]Tom MacWright ([21]@tmcw, [22]@tmcw@mastodon.social)
References:
[1] https://macwright.com/
[2] https://macwright.com/reading/
[3] https://macwright.com/photos/
[4] https://macwright.com/projects/
[5] https://macwright.com/drawings/
[6] https://macwright.com/micro/
[7] https://macwright.com/about/
[8] https://www.mapbox.com/mapbox-studio/
[9] https://observablehq.com/
[10] https://docs.mapbox.com/mapbox-gl-js/api/
[11] https://github.com/observablehq/runtime
[12] https://web.dev/interactive/
[13] https://engineering.shopify.com/blogs/engineering/solving-the-n-1-problem-for-graphql-through-batching
[14] https://blog.codinghorror.com/falling-into-the-pit-of-success/
[15] https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
[16] https://dev.to/richharris/in-defense-of-the-modern-web-2nia
[17] https://dev.to/devplebs/friday-night-deploys-22-a-brief-discussion-on-the-state-of-the-modern-web-2961
[18] https://frontendfirst.fm/episodes/read-and-discuss-second-guessing-the-modern-web
[19] https://medium.com/@kevinkirchner/a-ready-to-try-concept-in-response-to-second-guessing-the-modern-web-6946ec4d0598
[20] https://macwright.com/about/
[21] https://twitter.com/intent/follow?screen_name=tmcw&user_id=1458271
[22] https://mastodon.social/@tmcw