davideisinger.com

My personal website
Log | Files | Refs | README

macwright-com-vvkegs.txt (14200B)


      1 Tom MacWright
      2 
      3 [email protected]
      4 
      5 Tom MacWright
      6 
      7   • [1]Writing⇠
      8   • [2]Reading
      9   • [3]Photos
     10   • [4]Projects
     11   • [5]Drawings
     12   • [6]Micro
     13   • [7]About
     14 
     15 Second-guessing the modern web
     16 
     17 The emerging norm for web development is to build a React single-page
     18 application, with server rendering. The two key elements of this architecture
     19 are something like:
     20 
     21  1. The main UI is built & updated in JavaScript using React or something
     22     similar.
     23  2. The backend is an API that that application makes requests against.
     24 
     25 This idea has really swept the internet. It started with a few major popular
     26 websites and has crept into corners like marketing sites and blogs.
     27 
     28 I’m increasingly skeptical of it.
     29 
     30 There is a sweet spot of React: in moderately interactive interfaces. Complex
     31 forms that require immediate feedback, UIs that need to move around and react
     32 instantly. That’s where it excels. I helped build the editors in [8]Mapbox
     33 Studio and [9]Observable and for the most part, React was a great choice.
     34 
     35 But there’s a lot on either side of that sweet spot.
     36 
     37 The high performance parts aren’t React. [10]Mapbox GL, for example, is vanilla
     38 JavaScript and probably should be forever. The level of abstraction that React
     39 works on is too high, and the cost of using React - in payload, parse time, and
     40 so on - is too much for any company to include it as part of an SDK. Same with
     41 the [11]Observable runtime, the juicy center of that product: it’s very
     42 performance-intensive and would barely benefit from a port.
     43 
     44 The less interactive parts don’t benefit much from React. Listing pages, static
     45 pages, blogs - these things are increasingly built in React, but the benefits
     46 they accrue are extremely narrow. A lot of the optimizations we’re deploying to
     47 speed up these things, things like bundle splitting, server-side rendering, and
     48 prerendering, are triangulating what we had before the rise of React.
     49 
     50 And they’re kind of messy optimizations. Here are some examples.
     51 
     52 Bundle splitting.
     53 
     54 As your React application grows, the application bundle grows. Unlike with a
     55 traditional multi-page app, that growth affects every visitor: you download the
     56 whole app the first time that you visit it. At some point, this becomes a real
     57 problem. Someone who lands on the About page is also downloading 20 other pages
     58 in the same application bundle. Bundle splitting ‘solves’ this problem by
     59 creating many JavaScript bundles that can lazily load each other. So you load
     60 the About page and what your browser downloads is an ‘index’ bundle, and then
     61 that ‘index’ bundle loads the ‘about page’ bundle.
     62 
     63 This sort of solves the problem, but it’s not great. Most bundle splitting
     64 techniques require you to load that ‘index bundle’, and then only once that
     65 JavaScript is loaded and executed does your browser know which ‘page bundle’ it
     66 needs. So you need two round-trips to start rendering.
     67 
     68 And then there’s the question of updating code-split bundles. User sessions are
     69 surprisingly long: someone might have your website open in a tab for weeks at a
     70 time. I’ve seen it happen. So if they open the ‘about page’, keep the tab open
     71 for a week, and then request the ‘home page’, then the home page that they
     72 request is dictated by the index bundle that they downloaded last week. This is
     73 a deeply weird and under-discussed situation. There are essentially two
     74 solutions to it:
     75 
     76  1. You keep all generated JavaScript around, forever, and people will see the
     77     version of the site that was live at the time of their first page request.
     78  2. You create a system that alerts users when you’ve deployed a new version of
     79     the site, and prompt them to reload.
     80 
     81 The first solution has a drawback that might not be immediately obvious. In
     82 those intervening weeks between loading the site and clicking a link, you
     83 might’ve deployed a new API version. So the user will be using an old version
     84 of your JavaScript frontend with a new version of your API backend, and they’ll
     85 trigger errors that none of your testing knows about, because you’ll usually be
     86 testing current versions of each.
     87 
     88 And the second solution, while it works (and is what we implemented for Mapbox
     89 Studio), is a bizarre way for a web application to behave. Prompting users to
     90 ‘update’ is something from the bad old days of desktop software, not from the
     91 shiny new days of the web.
     92 
     93 Sure: traditional non-SPA websites are not immune to this pitfall. Someone
     94 might load your website, have a form open for many weeks, and then submit it
     95 after their session expired or the API changed. But that’s a much more limited
     96 exposure to failure than in the SPA case.
     97 
     98 Server-Side Rendering
     99 
    100 Okay, so the theory here is that SPAs are initially a blank page, which is then
    101 filled out by React & JavaScript. That’s bad for performance: HTML pages don’t
    102 need to be blank initially. So, Server-Side Rendering runs your JavaScript
    103 frontend code on the backend, creating a filled-out HTML page. The user loads
    104 the page, which now has pre-rendered content, and then the JavaScript loads and
    105 makes the page interactive.
    106 
    107 A great optimization, but again, caveats.
    108 
    109 The first is that the page you initially render is dead: you’ve created the 
    110 [12]Time To Interactive metric. It’s your startup’s homepage, and it has a
    111 “Sign up” button, but until the JavaScript loads, that button doesn’t do
    112 anything. So you need to compensate. Either you omit some interactive elements
    113 on load, or you try really hard to make sure that the JavaScript loads faster
    114 than users will click, or you make some elements not require JavaScript to work
    115 - like making them normal links or forms. Or some combination of those.
    116 
    117 And then there’s the authentication story. If you do SSR on any pages that are
    118 custom to the user, then you need to forward any cookies or
    119 authentication-relevant information to your API backend and make sure that you
    120 never cache the server-rendered result. Your formerly-lightweight application
    121 server is now doing quite a bit of labor, running React & making API requests
    122 in order to do this pre-rendering.
    123 
    124 APIs
    125 
    126 The dream of APIs is that you have generic, flexible endpoints upon which you
    127 can build any web application. That idea breaks down pretty fast.
    128 
    129 Most interactive web applications start to triangulate on “one query per page.”
    130 API calls being generic or reusable never seems to persist as a value in
    131 infrastructure. This is because a large portion of web applications are, at
    132 their core, query & transformation interfaces on top of databases. The hardest
    133 performance problems they tend to have are query problems and transfer
    134 problems.
    135 
    136 For example: a generically-designed REST API that tries not to mix ‘concerns’
    137 will produce a frontend application that has to make lots of requests to
    138 display a page. And then a new-age GraphQL application will suffer under the 
    139 [13]N+1 query problem at the database level until an optimization arrives. And
    140 a traditional “make a query and put it on a page” application will just, well,
    141 try to write some good queries.
    142 
    143 None of these solutions are silver bullets: I’ve worked with overly-strict REST
    144 APIs, optimization-hungry GraphQL APIs, and hand-crafted SQL APIs. But no
    145 option really lets a web app be careless about its data-fetching layer. Web
    146 applications can’t sit on top of independently-designed APIs: to have a chance
    147 at performance, the application and its datasource need to be designed as one.
    148 
    149 Data fetching
    150 
    151 Speaking of data fetching. It’s really important and really bizarre in React
    152 land. Years ago, I expected that some good patterns would emerge. Frankly, they
    153 didn’t.
    154 
    155 There are decent patterns in the form of GraphQL, but for a React component
    156 that loads data with fetch from an API, the solutions have only gotten weirder.
    157 There’s great documentation for everything else, but old-fashioned data loading
    158 is relegated to one example of how to mock out ‘fetch’ for testing, and lots of
    159 Medium posts of varying quality.
    160 
    161 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    162 
    163 Don’t read this as anti-React. I still think React is pretty great, and for a
    164 particular set of use cases it’s the best tool you can find. And I explicitly
    165 want to say that – from what I’ve seen – most other Single-Page-Application
    166 tools share most of these problems. They’re issues with the pattern, not the
    167 specific frameworks used to implement it. React alternatives have some great
    168 ideas, and they might be better, but they are ultimately really similar.
    169 
    170 But I’m at the point where I look at where the field is and what the
    171 alternative patterns are – taking a second look at unloved, unpopular, uncool
    172 things like Django, Rails, Laravel – and think what the heck is happening.
    173 We’re layering optimizations upon optimizations in order to get the SPA-like
    174 pattern to fit every use case, and I’m not sure that it is, well, worth it.
    175 
    176 And it should be easy to do a good job.
    177 
    178 Frameworks should lure people into the [14]pit of success, where following the
    179 normal rules and using normal techniques is the winning approach.
    180 
    181 I don’t think that React, in this context, really is that pit of success. A
    182 naïvely implemented React SPA isn’t stable, or efficient, and it doesn’t
    183 naturally scale to significant complexity.
    184 
    185 You can add optimizations on top of it that fix those problems, or you can use
    186 a framework like Next.js that will include those optimizations by default.
    187 That’ll help you get pretty far. But then you’ll be lured by all of the easy
    188 one-click ways to add bloat and complexity. You’ll be responsible for keeping
    189 some of these complex, finicky optimizations working properly.
    190 
    191 And for what? Again - there is a swath of use cases which would be hard without
    192 React and which aren’t complicated enough to push beyond React’s limits. But
    193 there are also a lot of problems for which I can’t see any concrete benefit to
    194 using React. Those are things like blogs, shopping-cart-websites, mostly-[15]
    195 CRUD-and-forms-websites. For these things, all of the fancy optimizations are
    196 trying to get you closer to the performance you would’ve gotten if you just
    197 hadn’t used so much technology.
    198 
    199 I can, for example, guarantee that this blog is faster than any Gatsby blog
    200 (and much love to the Gatsby team) because there is nothing that a React static
    201 site can do that will make it faster than a non-React static site.
    202 
    203 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    204 
    205 But the cultural tides are strong. Building a company on Django in 2020 seems
    206 like the equivalent of driving a PT Cruiser and blasting Faith Hill’s “Breathe”
    207 on a CD while your friends are listening to The Weeknd in their Teslas.
    208 Swimming against this current isn’t easy, and not in a trendy contrarian way.
    209 
    210 I don’t think that everyone’s using the SPA pattern for no reason. For large
    211 corporations, it allows teams to work independently: the “frontend engineers”
    212 can “consume” “APIs” from teams that probably work in a different language and
    213 can only communicate through the hierarchy. For heavily interactive
    214 applications, it has real benefits in modularity, performance, and structure.
    215 And it’s beneficial for companies to shift computing requirements from their
    216 servers to their customers browsers: a real win for reducing their spend on
    217 infrastructure.
    218 
    219 But I think there are a lot of problems that are better solved some other way.
    220 There’s no category winner like React as an alternative. Ironically, backends
    221 are churning through technology even faster than frontends, which have been
    222 loyal to one programming language for decades. There are some age-old
    223 technologies like Rails, Django, and Laravel, and there are a few halfhearted
    224 attempts to do templating and “serve web pages” from Go, Node, and other new
    225 languages. If you go this way, you’re beset by the cognitive dissonance of
    226 following in the footsteps of enormous projects - Wikipedia rendering web pages
    227 in PHP, Craigslist rendering webpages in Perl - but being far outside the norms
    228 of modern web development. If Wikipedia were started today, it’d be React.
    229 Maybe?
    230 
    231 What if everyone’s wrong? We’ve been wrong before.
    232 
    233 Follow-ups & commmentary
    234 
    235   • [16]"In defense of the modern web", Rich Harris
    236   • [17]Friday Night Deploys (Podcast) #22: A Brief Discussion On The State Of
    237     The Modern Web
    238   • [18]Frontend First (Podcast): Read & Discuss
    239   • [19]A Ready-To-Try Concept in Response to “Second-guessing the modern web”
    240 
    241 May 10, 2020  [20]Tom MacWright ([21]@tmcw, [22]@[email protected])
    242 
    243 
    244 References:
    245 
    246 [1] https://macwright.com/
    247 [2] https://macwright.com/reading/
    248 [3] https://macwright.com/photos/
    249 [4] https://macwright.com/projects/
    250 [5] https://macwright.com/drawings/
    251 [6] https://macwright.com/micro/
    252 [7] https://macwright.com/about/
    253 [8] https://www.mapbox.com/mapbox-studio/
    254 [9] https://observablehq.com/
    255 [10] https://docs.mapbox.com/mapbox-gl-js/api/
    256 [11] https://github.com/observablehq/runtime
    257 [12] https://web.dev/interactive/
    258 [13] https://engineering.shopify.com/blogs/engineering/solving-the-n-1-problem-for-graphql-through-batching
    259 [14] https://blog.codinghorror.com/falling-into-the-pit-of-success/
    260 [15] https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
    261 [16] https://dev.to/richharris/in-defense-of-the-modern-web-2nia
    262 [17] https://dev.to/devplebs/friday-night-deploys-22-a-brief-discussion-on-the-state-of-the-modern-web-2961
    263 [18] https://frontendfirst.fm/episodes/read-and-discuss-second-guessing-the-modern-web
    264 [19] https://medium.com/@kevinkirchner/a-ready-to-try-concept-in-response-to-second-guessing-the-modern-web-6946ec4d0598
    265 [20] https://macwright.com/about/
    266 [21] https://twitter.com/intent/follow?screen_name=tmcw&user_id=1458271
    267 [22] https://mastodon.social/@tmcw