In the first two parts of this series, I wrote about how to use reactjs together with spring boot on a GraalVM and how to use the same routing / navigation on the server side and in the browser.
Today, I want to look into whether rendering on the server side is even worth it. TL;DR: Out-of-the-box, the improvement in time-to-first-render was not that that much. But with a few tricks, I was able to reduce the time by ~60%.
After the last blog post, a friend gave me some great feedback. And one of the things he wrote was “But you do know that googlebot also executes JavaScript?”
My response was yes, but server-side rendering is not only about search engine optimization, it should also improve the time to first render when you had never opened the site before.
Add Some CSS
And after that conversation, I decided to measure the time to first render (over a simulated “Slow 3G” network) and tweak the code a bit. I added some CSS in client/src/MainNavigation.css
to make the example a little bit more realistic. This is in the commit 4513dee3f0135575cca7bb272b74d8bd44a44832.
.navigation {
display: flex;
list-style: none;
padding: 0;
}
.navigation li {
margin: 0;
padding: 0;
}
.navigation li::after {
display: inline;
content: "|";
padding: 0 0.5em;
}
.navigation li:last-child::after {
content: "";
}
I am adding this CSS in client/src/MainNavigation.tsx
as a webpack import:
import React from 'react'
import { NavLink } from 'react-router-dom'
import './MainNavigation.css'
export const MainNavigation = () => {
return (<ul className="navigation">
<li><NavLink to="/">Home</NavLink></li>
<li><NavLink to="/r/about">About</NavLink></li>
<li><NavLink to="/r/list">List</NavLink></li>
</ul>)
}
Client-Side Rendering Only
Here is the result from the production webpack build, without any server-side rendering:
5.28 seconds for rendering on the client only - Surely we can do better than that when using server-side rendering, right?
Server-Side Rendering
4.08 seconds for server-side rendering with no modification - That’s not so much better than before. But let’s look into why this is slower. Rendering is waiting for the CSS and the JavaScript to load.
Defer JavaScript Loading
What if I defer loading all JavaScript? That should improve performance a little bit… But there is some work to do. When I defer the scripts, the function that I want to call in client/public/index.html
might not exsit yet - I must wait for it to be defined:
<script type="module">
function renderWhenAvailable() {
if(window.renderApp) {
window.renderApp()
} else {
window.setTimeout(renderWhenAvailable, 100)
}
}
if(window.isServer) {
window.renderAppOnServer()
} else {
renderWhenAvailable()
}
</script>
After that, I can modify my kotlin code (Controller.kt
) to add defer="defer"
to all scripts:
@Controller
class HtmlController {
val indexHtml by lazy {
HtmlController::class.java.getResource("/reactapp/index.html").readText()
.replace("<script", "<script defer=\"defer\"")
}
}
I did that in the commit e81d57bfdf4f18379a5719133055c35459089b63. Is it better?
4.11 seconds for server-side rendering and deferring all scripts. That’s not better. The renderer is still waiting for the CSS to load.
Inline CSS With styled-components
Can we improve that? We would have to copy all the CSS code into the HTML file itself - Something that styled-components does by default. So, I changed client/src/MainNavigation.tsx
to use styled-components:
import React from 'react'
import { NavLink } from 'react-router-dom'
import styled from 'styled-components'
const Nav = styled.ul`
display: flex;
list-style: none;
padding: 0;
`
const Item = styled.li`
margin: 0;
padding: 0;
::after {
display: inline;
content: "|";
padding: 0 0.5em;
}
:last-child::after {
content: "";
}
`
export const MainNavigation = () => {
return (<Nav>
<Item><NavLink to="/">Home</NavLink></Item>
<Item><NavLink to="/r/about">About</NavLink></Item>
<Item><NavLink to="/r/list">List</NavLink></Item>
</Nav>)
}
and tried again (commit 531aaf53494de63fbcb57b99bb924694b96d0d44):
2.15 seconds for server-side rendering and deferring all scripts and using styled-components. Now that’s not bad. The web site renders immediately after loading the HTML file, and then again when all react components were successfully hydrated. I think I cannot do better than that right now.
Read / watch all parts of “Spring and Isomorphic React” here: