About a year ago, I decided to create a portfolio website with Next.js. I did this
mostly to get familiar with the technology, but also to have a playground to test
new features (which I could later implement at work).
But a month ago I started seeing a lot of praise for Remix on Twitter, and instantly wanted to give it a try.
Remix is described by the founders as follows:
Remix is a full stack web framework that lets you focus on the user interface and work back through web fundamentals to deliver a fast, slick, and resilient user experience.
The first thing I wanted to test were the nested routes, which seemed like one of the top features of Remix.
I use nested routes to create my contact page.
So in my /routes/contact.tsx file I define the general structure of my contact page, with the parts I always want visible (in this case it’s the <img>) in it.
The <Outlet> is a special component from Remix which indicates where the nested routes should be rendered on your page.
In /routes/contact/index.tsx I’m defining what should be shown inside the <Outlet> initially. This is a simple form, with some Remix magic added to it (I’ll get into this later).
You can see I’m executing an API call to Formspree, and once it’s finished, I want to show a thank you message.
By doing the redirect (return redirect('/contact/thanks')), I’m telling Remix to render the route /contact/thanks.tsx inside the <Outlet> instead of /contact/index.tsx.
Easy peasy!
Another difference between Next.js & Remix is that in Remix everything is server side rendered by default, while Next.js gives you the choice to create static builds (SSG/ISR).
Coming from Next.js where I was used to using incremental static regeneration, this kind of scared me. What if my API calls are slow? What if my API is down?
For the API being slow part there is no real solution, but there’s an option to add caching headers to the response, so the API only gets hit every so often.
Example (this should be added to the route you want to cache on client/CDN):
Data loading should be done at the server side by default too (so we can prerender the complete HTML document before sending it to the client).
To load data on the server, Remix provides a function called loader and a hook called useLoaderData to consume the data in your component.
Example for my blog route:
You can see I’m loading the data from Dev.to through the loader function, and then consuming it using the useLoaderData hook. That’s all there is to it! Remix polyfills fetch so you don’t have to worry about using node-fetch.
Remix also provides the option to leave out all javascript, for your whole application, or just for some routes.
This is handled by putting the <Scripts> tag in your /app/root.tsx, if you leave it out, no javascript will be loaded on your pages. I did this on my website, and everything still works as expected (data loading, contact form, setting cookies…).
Managing & setting cookies is also a breeze in Remix.
The following parts are needed for a cookie to store the choice of theme on my site:
Using this code, I am able to get my theme cookie when the website is loaded (in /app/root.tsx), and I can change the styling of my website based on this.
I can also change the theme by using a button in my navigation:
By using the <form method="post" action="/"> I tell Remix to use the action defined in /app/root.tsx, and pass on the current URL, so the user gets redirected to the same URL, but with the cookie set!
I know this isn’t ideal for animating the transition of the theme etc, but it works without JS, and that was my main goal at this time.
Some Lighthouse stats (both hosted on Vercel):
Next.js:
Remix:
Both very fast, but Remix seems to really get the TTI a lot lower, probably because the load a lot of the needed resources in parallel, and partly also because no JS is loaded.