UPDATE: [the comparison-post is published is now live](https://blog.runar.dev/rewriting-older-react-spa-to-fast-ssr-comparing-sveltekit-and-nextjs?showSharer=true)
In an upcoming post, I'll be sharing my experiences in this regard, as well as how well SvelteKit and NextJS is up for the task.
About the legacy-application
The reason for considering a rewrite at this point is that the web application has reached many different potentially blocking problems. These problems can individually be solved, but in total, it would require significant work to retrofit everything.
The API used is a bit slow, and because data is currently loaded in each component, this has a drastic waterfall effect, and the application is therefore quite slow. A typical page load takes about 10-12 seconds. For instance:
A legacy custom component-library, forked from an older version of Ionic, with some random upgrades and changes.
Spaghetti-code everywhere with legacy hacks which makes further development slow and potentially breaking.
Requires JS at all times.
Often breaks for clients as we update the application since the hashed filenames are no longer available.
Waterfall Typically, the current application loads somewhat like this:
A static HTML is sent to the user, with references to some js-files
Initial JS is loaded for the initial page. Includes React, Keycloak.js for handling login, refresh-tokens etc, CSS, a few minor components and possibly other libraries.
Keycloak.js checks if the current user is logged in. It uses an iframe for this, which is attached to the current page and then it passes information from the iframe. If the user is logged in, the application will receive an access-token, id-token and a refresh-token. These are quite large in our stack.
if the user is not logged in, a redirect to the login page (different subdomain) occurs. The user logs in and is then redirected back to the application. This starts the flow at the beginning, but most of the initial resources should now be cached, so it should be a bit faster.
The user is now logged in, and an initial GraphQL-call is made, with the access-token attached as one of the headers for authentication. The result of this call is then used as an indication of what the user has access to so that we can further load the correct data. In the meantime, the user just sees a big loading-indicator.
Now the application works like a web application. Navigation only occurs on client-side, code is lazy-loaded (some with preloading), and data is loaded when components mount. For some components, this has the effect that data will not load until the parent has completed loading and rendering.
Broad strokes as planned rewrite
As a way to make the application feel faster for the user, I would like the create a proof-of-concept for a mainly server-rendered application. Adding client-side hydration should be optional and considered an enhancement of the main experience. Luckily, Both SvelteKit and Next.js makes are both tools for accomplishing this.
Authentication should be handled server-side, with cookies.
The cookie should be only a unique ID (preferably a short one, like nanoid or similar), and used as a reference for the JWT-tokens, of which the server will keep track of. - This drops the client-dependency for keycloak.js. - No need for the user to load the initial page to only be redirected to the login-domain, we can redirect the user at once by simply checking the cookie client-side.
All data-loading should happen at page-level, and not component-level.
This reduces a lot of waterfall effects, and it reduces complexity.
Data should be eagerly preloaded.
If we can detect that the user was previously logged in as user X, but their login (token) has expired and the user needs to log in again, we can assume that the user probably will log in as the same user. We can then start to preload data server-side for that user so that when the user completes the login, that information is already loaded and can be delivered faster.
Preload data likely needed for further navigation.
Stale While Reloading. The server should be able to cache data between eloads, and probably for a longer time. As the user requests the data, it is served from the cache and also reloaded on the server.
Possibly stream the updated data back to the client as an update.
Add an expiry for each set of data, so that if the server's data is considered stale, the user still needs to wait for updated data instead of seeing old data.
Possibly add some kind of queue that would keep the cache updated for logged-in users.
Reduce reliance on client-side-js
The page should work without js. This is not a hard requirement, as I believe some parts of the UI will either be difficult to implement with only HTML or lead to a very different UI, I believe the goal itself will help with reducing the temptation to add unnecessary client-side libraries that mostly serve as bloat and bloat.
Use native web standards as far as possible, like forms.
For UI frameworks, it should be CSS-first.
Investigate further opportunities
Since we are now using server-side-rendering, this can enable some opportunities that are now more readily available. Of course, we already have an API server, so it's not like this was impossible before, but since both SvelteKit and Next.JS has server-code that is available directly next to the client-code (with the possibility of sharing types), it should make it a lot easier to add some functionality very fast without going through other microservices.
I'm not advocating going back to a monolith here, only keeping in mind the possibilities and the improved developer experience. Also, some of these are just not possible with the legacy stack.
Developer-page for quickly impersonating specific users
- As developers, we often do a lot of testing with different test-users, each of which has different sets of data applied, to comply with different scenarios. We can already impersonate test-users, but the process is a bit cumbersome. This can be added fairly easily to this new application, where one only needs to search for the user (or directly link to them) to impersonate them, removing a lot of steps for the developer.
Developer-page for shared views into the database
- We can add a simple ORM or similar for quickly adding views directly from the database, skipping the API-layer completely.
Since the original web application is written as a React SPA, moving it to NextJS may save us some work since we can reuse at least parts of the original code. Also, we already have developers fluently writing React applications, so there is less new stuff to learn.
NextJS and React are established technologies used by big players.
SvelteKit has a deep focus on using standard web technology and API, so pages and components are typically using mostly standard HTML and regular JS, and the complexity of managing the framework is less dominant. For instance, with React, my experience has been that both I and other members of the team quite often are using react-specific APIs like useEffect, useCallback and such which often leads to a bit of extra work that seems unscary. SvelteKit lets us write more application-specific code. For the parts that are Svelte-specific, they are typically just simple calls or annotations.
Since Svelte compiles and only bundles the parts of the framework that is in actual use, the bundle size should be smaller.
Some say SvelteKit is faster than React. That may very well be, but React is still plenty fast if used correctly. But that is the kicker, there are so many ways in React to shoot oneself in the foot that may lead to hard-to-debug performance problems down the road. I'm not saying this is impossible with Svelte, I just have not experienced it while writing Svelte code. Mostly I don't even think about writing in Svelte, I am just writing the application code itself.
Although Svelte and SvelteKit both are quite new, there has been a lot of shift lately and more and more bigger players are starting to use it. At this point in time, I believe SvelteKit is a fair bet.
Their documentation and official tutorials are great.