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