{"id":2200,"date":"2024-05-15T17:26:46","date_gmt":"2024-05-15T23:26:46","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2200"},"modified":"2024-05-21T16:17:28","modified_gmt":"2024-05-21T22:17:28","slug":"prefetching-when-server-loading-wont-do","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/prefetching-when-server-loading-wont-do\/","title":{"rendered":"Prefetching When Server Loading Won&#8217;t Do"},"content":{"rendered":"\n<p>This is a post about a boring* topic: loading data.<\/p>\n\n\n\n<p><small>(* Just kidding it will be amazing and engaging.)<\/small><\/p>\n\n\n\n<p>Not&nbsp;<em>how<\/em>&nbsp;to load data, but instead we&#8217;ll take a step back, and look at&nbsp;<em>where<\/em>&nbsp;to load data. Not in any particular framework, either, this is going to be more broadly about data loading in different web application architectures, and paricularly how that impacts performance.<\/p>\n\n\n\n<p>We&#8217;ll start with client-rendered sites and talk about some of the negative performance characteristics they may have. Then we&#8217;ll move on to server-rendered apps, and then to the lesser-known out-of-order streaming model. To wrap up, we&#8217;ll talk about a surprisingly old, rarely talked about way to effectively load&nbsp;<em>slow<\/em>&nbsp;data in a server-rendered application. Let&#8217;s get started!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Client Rendering<\/h2>\n\n\n\n<p>Application metaframeworks like&nbsp;<a href=\"https:\/\/nextjs.org\/\">Next<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/kit.svelte.dev\/\">SvelteKit<\/a>&nbsp;have become incredibly popular. In addition to offering developer conveniences like file system-based routing and scaffolding of API endoints, they also, more importantly, allow you to server render your application.<\/p>\n\n\n\n<p>Why is server rendering so important? Let&#8217;s take a look at how the world looks with the opposite: client-rendered web applications, commonly referred to as &#8220;single page applications&#8221; or SPAs. Let&#8217;s start with a simplified diagram of what a typical request for a page looks like in an SPA.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"985\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?resize=985%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-2323\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?resize=985%2C1024&amp;ssl=1 985w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?resize=289%2C300&amp;ssl=1 289w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?resize=768%2C798&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?resize=1478%2C1536&amp;ssl=1 1478w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-1.png?w=1738&amp;ssl=1 1738w\" sizes=\"auto, (max-width: 985px) 100vw, 985px\" \/><\/figure>\n\n\n\n<p>The browser makes a request to your site. Let&#8217;s call it&nbsp;<code>yoursite.io<\/code>. With an SPA, it usually sends down a single,&nbsp;<em>mostly empty<\/em>&nbsp;HTML page, which has whatever script and style tags needed to run the site. This&nbsp;<em>shell<\/em>&nbsp;of a page might display your company logo, your static header, your copyright message in the footer, etc. But mostly it exists to load and run JavaScript, which will build the &#8220;real&#8221; site.<\/p>\n\n\n\n<p class=\"learn-more\">This is why these sites are called &#8220;single page&#8221; applications. There&#8217;s a single web page for the whole app, which runs code on the client to detect URL changes, and request and render whatever new UI is needed.<\/p>\n\n\n\n<p>Back to our diagram. The inital web page was sent back from the web server as HTML. Now what? The browser will parse that HTML and find script tags. These script tags contain our application code, our JavaScript framework, etc. The browser will send requests back to the web server to load&nbsp;<em>these<\/em>&nbsp;scripts. Once the browser gets them back, it&#8217;ll parse, and execute them, and in so doing, begin executing your application code.<\/p>\n\n\n\n<p>At this point whatever client-side router you&#8217;re using (i.e.&nbsp;<a href=\"https:\/\/reactrouter.com\/en\/main\">react-router<\/a>,&nbsp;<a href=\"https:\/\/tanstack.com\/router\/latest\">Tanstack Router<\/a>, etc) will render your current page.<\/p>\n\n\n\n<p>But there&#8217;s no data yet!<\/p>\n\n\n\n<p>So you&#8217;re probably displaying loading spinners or skeleton screens or the like. To get the data, your client-side code will&nbsp;<em>now<\/em>&nbsp;make&nbsp;<em>yet another<\/em>&nbsp;request to your server to fetch whatever data are needed, so you can display your real, finished page to your user. This could be via a plain old&nbsp;<code>fetch<\/code>,&nbsp;<a href=\"https:\/\/tanstack.com\/query\/latest\/docs\/framework\/react\/overview\">react-query<\/a>, or whatever. Those details won&#8217;t concern us here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SSR To The Rescue<\/h2>\n\n\n\n<p>There is a pretty clear solution here. The server&nbsp;<em>already has<\/em>&nbsp;has the URL of the request, so instead of only returning that shell page, it could (should) request the data as well, get the page all ready to go, and send down the complete page.<\/p>\n\n\n\n<p>Somehow.<\/p>\n\n\n\n<p>This is how the web always worked with tools like PHP or asp.net. But when your app is written with a client-side JavaScript framework like React or Svelte, it&#8217;s surprisingly tricky. These frameworks all have API&#8217;s for stringifying a component tree into HTML on the server, so that markup can be sent down to the browser. But if a component in the middle of that component tree needs data, how do you load it on the server, and then somehow inject it where it&#8217;s needed? And then have the client acknowledge that data, and not re-request it. And of course, once you solve these problems and render your component tree, with data, on the server, you&nbsp;<em>still<\/em>&nbsp;need to&nbsp;<em>re-render<\/em>&nbsp;this component tree on the client, so your client-side code, like event handlers and such, start working.<\/p>\n\n\n\n<p>This act of re-rendering the app client side is called&nbsp;<em>hydration<\/em>. Once it&#8217;s happened, we say that our app is&nbsp;<em>interactive<\/em>. Getting these things right is one of the main benefits modern application meta-frameworks like Next and SvelteKit provide.<\/p>\n\n\n\n<p>Let&#8217;s take a look at what our request looks like in this server-rendered setup:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"828\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?resize=1024%2C828&#038;ssl=1\" alt=\"\" class=\"wp-image-2324\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?resize=1024%2C828&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?resize=300%2C243&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?resize=768%2C621&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?resize=1536%2C1243&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2.png?w=1738&amp;ssl=1 1738w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>That&#8217;s&nbsp;<em>great<\/em>. The user sees the full page much, much sooner. Sure, it&#8217;s not&nbsp;<em>interactive<\/em>&nbsp;yet, but if you&#8217;re not shipping down obscene amounts of JavaScript, there&#8217;s a&nbsp;<em>really<\/em>&nbsp;good chance hydration will finish before the user can manage to click on any buttons.<\/p>\n\n\n\n<p>We won&#8217;t get into all this, but Google themselves tell you this is much better for SEO as well.<\/p>\n\n\n\n<p>So, what&#8217;s the catch? Well, what if our data are&nbsp;<strong>slow to load<\/strong>. Maybe our database is busy. Maybe it&#8217;s a huge request. Maybe there is a network hiccup. Or maybe you just depend on slow services you can&#8217;t control. It&#8217;s not rare.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"985\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?resize=985%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-2325\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?resize=985%2C1024&amp;ssl=1 985w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?resize=289%2C300&amp;ssl=1 289w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?resize=768%2C798&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?resize=1478%2C1536&amp;ssl=1 1478w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-2-1.png?w=1738&amp;ssl=1 1738w\" sizes=\"auto, (max-width: 985px) 100vw, 985px\" \/><\/figure>\n\n\n\n<p>This might be&nbsp;<em>worse<\/em>&nbsp;than the SPA we started with. Even though we needed multiple round trips to the server to get data, at least we were displaying a shell of a page quickly. Here, the initial request to the server will just hang and wait as long as needed for that data to load on the server, before sending down the full page. To the user, their browser (and your page) could appear unresponsive, and they might just give up and go back.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Out of Order Streaming<\/h2>\n\n\n\n<p>What if we could have the best of all worlds. What if we could server render, like we saw.&nbsp;<em>But<\/em>&nbsp;if some data are slow to load, we ship the rest of the page, with the data that we have, and let the server&nbsp;<em>push<\/em>&nbsp;down the remaining data, when ready. This is called streaming, or more precisely, out-of-order streaming (streaming, without the out-of-order part, is a separate, much more limited thing which we won&#8217;t cover here).<\/p>\n\n\n\n<p>Let&#8217;s take a hypothetical example where the\u00a0<code>data abd<\/code>, and\u00a0<code>data xyz<\/code>\u00a0are slow to load.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"540\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?resize=1024%2C540&#038;ssl=1\" alt=\"\" class=\"wp-image-2333\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?resize=1024%2C540&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?resize=300%2C158&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?resize=768%2C405&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?resize=1536%2C810&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-3.png?w=1738&amp;ssl=1 1738w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>With out-of-order streaming we can load the to-do data load on the server, and send the page with just that data down to the user, immediately. The other two pieces of data have not loaded, yet, so our UI will display some manner of loading indicator. When the next piece of data is ready, the server pushes it down:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"788\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?resize=1024%2C788&#038;ssl=1\" alt=\"\" class=\"wp-image-2335\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?resize=1024%2C788&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?resize=300%2C231&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?resize=768%2C591&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?resize=1536%2C1182&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/Frame-1-1-2.png?w=1738&amp;ssl=1 1738w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the catch?<\/h3>\n\n\n\n<p>So does this solve all of our problems? Yes, but&#8230; only if the framework you&#8217;re using supports it. To stream&nbsp;<a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/routing\/loading-ui-and-streaming\">with Next.js app directory<\/a>&nbsp;you&#8217;ll use Suspense components with&nbsp;<a href=\"https:\/\/react.dev\/reference\/rsc\/server-components\">RSC<\/a>.&nbsp;<a href=\"https:\/\/kit.svelte.dev\/docs\/load#streaming-with-promises\">With SvelteKit<\/a>&nbsp;you just return a promise from your loader.&nbsp;<a href=\"https:\/\/remix.run\/docs\/en\/main\/guides\/streaming\">Remix supports this too<\/a>, with an API that&#8217;s in the process of changing, so check their docs.&nbsp;<a href=\"https:\/\/start.solidjs.com\/getting-started\/what-is-solidstart\">SolidStart<\/a>&nbsp;will also support this, but as of writing that entire project is still in beta, so check its docs when it comes out.<\/p>\n\n\n\n<p>Some frameworks do not support this, like&nbsp;<a href=\"https:\/\/astro.build\/\">Astro<\/a>&nbsp;and Next if you&#8217;re using the legacy&nbsp;<code>pages<\/code>&nbsp;directory.<\/p>\n\n\n\n<p>What if we&#8217;re using those projects, and we have some dependencies on data which are slow to load? Are we stuck rendering this data in client code, after hydration?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prefetching to the rescue<\/h2>\n\n\n\n<p>The web platform has a feature called&nbsp;<a href=\"https:\/\/caniuse.com\/link-rel-prefetch\">prefetching<\/a>. This lets us add a&nbsp;<code>&lt;link&gt;<\/code>&nbsp;tag to the&nbsp;<code>&lt;head&gt;<\/code>&nbsp;section of our HTML page, with a&nbsp;<code>rel=\"prefetch\"<\/code>&nbsp;attribute, and an&nbsp;<code>href<\/code>&nbsp;attribute of the URL we want to prefetch. We can put service endpoint calls here, so long as they use the GET verb. If we need to pre-fetch data from an endpoint that uses POST, you&#8217;ll need to proxy it through an endpoint that uses GET. It&#8217;s worth noting that you can also prefetch with an HTTP header if that&#8217;s more convenient; see&nbsp;<a href=\"https:\/\/web.dev\/articles\/codelab-two-ways-to-prefetch\">this post<\/a>&nbsp;for more information.<\/p>\n\n\n\n<p>When we do this, our page will start pre-fetching our resources as soon as the browser parses the link tag. Since it&#8217;s in the&nbsp;<code>&lt;head&gt;<\/code>, that means it&#8217;ll start pre-fetching at the same time our scripts and stylesheets are requested. So we no longer need to wait until our script tags load, parse, and hydrate our app. Now the data we need will start pre-fetching immediately. When hydration does complete, and our application code requests those same endpoints, the browser will be smart enough to serve that data from the&nbsp;<em>prefetch cache<\/em>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Let&#8217;s see prefetching in action<\/h3>\n\n\n\n<p>To see pre-fetching in action, we&#8217;ll use&nbsp;<a href=\"https:\/\/astro.build\/\">Astro<\/a>. Astro is a wonderful web framework that doesn&#8217;t get nearly enough attention. One of the very few things it can&#8217;t do is out-of-order streaming (for now). But let&#8217;s see how we can improve life with pre-fetching.<\/p>\n\n\n\n<p>The repo for the code I&#8217;ll be showing is&nbsp;<a href=\"https:\/\/github.com\/arackaf\/prefetch-blog-post-astro\">here<\/a>. It&#8217;s not deployed anywhere, for fear of this blog posting getting popular, and me getting a big bill from Vercel. But the project has no external dependencies, so you can clone, install, and run locally. You could also deploy this to Vercel yourself if you really want to see it in action.<\/p>\n\n\n\n<p>I whipped up a very basic, very ugly web page that hits some endpoints to pull down a hypothetical list of books, and some metadata about the library, which renders the books once ready. It looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img7a-book-list.jpg?resize=1024%2C538&#038;ssl=1\" alt=\"\" class=\"wp-image-2208\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img7a-book-list.jpg?resize=1024%2C538&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img7a-book-list.jpg?resize=300%2C157&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img7a-book-list.jpg?resize=768%2C403&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img7a-book-list.jpg?w=1524&amp;ssl=1 1524w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>The endpoints return static data, which is why there&#8217;s no external dependencies. I added a manual delay of 700ms to these endpoints (sometimes you have slow services and there&#8217;s nothing you can do about it), and I also installed and imported some large JavaScript libraries (d3, framer-motion, and recharts) to make sure hydration would take a moment or two, like with most production applications. And since these endpoints are slow, they&#8217;re a poor candidate for server fetching.<\/p>\n\n\n\n<p>So let&#8217;s request them client-side, see the performance of the page, and then add pre-fetching to see how that improves things.<\/p>\n\n\n\n<p>The client-side fetching looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  fetch(<span class=\"hljs-string\">\"\/api\/books\"<\/span>)\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">resp<\/span>) =&gt;<\/span> resp.json())\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">books<\/span>) =&gt;<\/span> {\n      setBooks(books);\n    });\n\n  fetch(<span class=\"hljs-string\">\"\/api\/books-count\"<\/span>)\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">resp<\/span>) =&gt;<\/span> resp.json())\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">booksCountResp<\/span>) =&gt;<\/span> {\n      setCount(booksCountResp.count);\n    });\n}, &#91;]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Nothing fancy. Nothing particularly resilient here. Not even any error handling. But perfect for our purposes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Network diagram without pre-fetching<\/h3>\n\n\n\n<p>Running this project, deployed to Vercel, my network diagram looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"164\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=1024%2C164&#038;ssl=1\" alt=\"\" class=\"wp-image-2209\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=1024%2C164&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=300%2C48&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=768%2C123&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=1536%2C247&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img8-network-diagram-no-prefetch.jpg?resize=2048%2C329&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Notice all of the script and style resources, which need to be requested and processed before our client-side fetches start (on the last two lines).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adding pre-fetching<\/h3>\n\n\n\n<p>I&#8217;ve added a second page to this project, called&nbsp;<code>with-prefetch<\/code>, which is the same as the index page. Except now, let&#8217;s see how we can add some&nbsp;<code>&lt;link&gt;<\/code>&nbsp;tags to request these resources sooner.<\/p>\n\n\n\n<p>First, in the root layout, let&#8217;s add this in the head section<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">slot<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"head\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">slot<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>this gives us the ability to (but does not require us to) add content to our HTML document&#8217;s&nbsp;<code>&lt;head&gt;<\/code>. This is exactly what we need. Now we can make a&nbsp;<code>PrefetchBooks<\/code>&nbsp;React component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> type { FC } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> PrefetchBooks: FC&lt;{}&gt; = <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"prefetch\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/api\/books\"<\/span> <span class=\"hljs-attr\">as<\/span>=<span class=\"hljs-string\">\"fetch\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"prefetch\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/api\/books-count\"<\/span> <span class=\"hljs-attr\">as<\/span>=<span class=\"hljs-string\">\"fetch\"<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  );\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then render it in our prefetching page, like so<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;PrefetchBooks slot=<span class=\"hljs-string\">\"head\"<\/span> \/&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note the&nbsp;<code>slot<\/code>&nbsp;attribute on the React component, which tells Astro (not React) where to put this content.<\/p>\n\n\n\n<p>With that, if we run&nbsp;<em>that<\/em>&nbsp;page, we&#8217;ll see our link tags in the head<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"866\" height=\"394\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img9-link-in-head.jpg?resize=866%2C394&#038;ssl=1\" alt=\"\" class=\"wp-image-2210\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img9-link-in-head.jpg?w=866&amp;ssl=1 866w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img9-link-in-head.jpg?resize=300%2C136&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img9-link-in-head.jpg?resize=768%2C349&amp;ssl=1 768w\" sizes=\"auto, (max-width: 866px) 100vw, 866px\" \/><\/figure>\n\n\n\n<p>Now let&#8217;s look at our updated network diagram:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"141\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch.jpg?resize=1024%2C141&#038;ssl=1\" alt=\"\" class=\"wp-image-2211\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch-scaled.jpg?resize=1024%2C141&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch-scaled.jpg?resize=300%2C41&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch-scaled.jpg?resize=768%2C106&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch-scaled.jpg?resize=1536%2C212&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img10-network-diagram-with-prefetch-scaled.jpg?resize=2048%2C282&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Notice our endpoint calls now start immediately, on lines 3 and 4. Then later, in the last two lines, we see the real fetches being executed, at which point they just latch onto the prefetch calls already in flight.<\/p>\n\n\n\n<p>Let&#8217;s put some hard numbers on this. When I ran a webpagetest mobile Lighthouse analysis on the version of this page without the pre-fetch, I got the following.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"186\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before.jpg?resize=1024%2C186&#038;ssl=1\" alt=\"\" class=\"wp-image-2212\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before-scaled.jpg?resize=1024%2C186&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before-scaled.jpg?resize=300%2C55&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before-scaled.jpg?resize=768%2C140&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before-scaled.jpg?resize=1536%2C279&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img11-lighthouse-before-scaled.jpg?resize=2048%2C372&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Note the LCP (Largest Contentful Paint) value. That&#8217;s essentially telling us when the page looks finished to a user. Remember, the Lighthouse test simulates your site in the slowest mobile device imagineable, which is why it&#8217;s 4.6 seconds.<\/p>\n\n\n\n<p>When I re-run the same test on the pre-fetched version, things improved about a second<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"183\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after.jpg?resize=1024%2C183&#038;ssl=1\" alt=\"\" class=\"wp-image-2213\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after-scaled.jpg?resize=1024%2C183&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after-scaled.jpg?resize=300%2C54&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after-scaled.jpg?resize=768%2C138&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after-scaled.jpg?resize=1536%2C275&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/img12-lighthouse-after-scaled.jpg?resize=2048%2C367&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Definitely much better, but still not&nbsp;<em>good<\/em>; but it never will be until you can get your backend fast. But with some intelligent, targetted pre-fetching, you can at least improve things.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Parting thoughts<\/h2>\n\n\n\n<p>Hopefully all of your back-end data requirements will be forever fast in your developer journeys. But when they&#8217;re not, prefetching resources is a useful tool to keep in your toolbelt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s get into the challenges and strategies of loading data in web applications, focusing on the comparison between client-rendered sites and server-rendered applications. There are some clear negative performance impacts of SPAs and server-rendered apps are *usually* better here, except when data is slow. Then what?<\/p>\n","protected":false},"author":21,"featured_media":2217,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[174,109,127,162],"class_list":["post-2200","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-data","tag-next-js","tag-streaming-html","tag-sveltekit"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/05\/image-3.png?fit=1792%2C1024&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2200","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2200"}],"version-history":[{"count":7,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2200\/revisions"}],"predecessor-version":[{"id":2336,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2200\/revisions\/2336"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2217"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2200"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2200"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2200"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}