frrrip throwing remix.run into the mix: my first impressions
remix.run is the (or not, to be confirmed) hot new Full Stack React framework around the block. It is hyped by a few knowledgeable engineers at the moment. The founders Michael Jackson and Ryan Florence are mostly known for React Router, UNPKG, and Reach UI. Moreover, Kent C. Dodds has recently joined the team as director of developer experience to throw some more wood on the fire. Many developers have used at least one or more things that they made.
In this post, I will be talking about:
About Remix
The entire idea behind Remix is that everything is server-rendered. As someone who has spent much time using things like NextJS and Gatsby, going mainly SSR is somewhat different.
Their website says: Remix — Build better websites. Saying something like that lifts the expectations and raises the bar to a high level, especially when the competition for creating websites is enormous. NextJS is probably the closest competitor for what Remix is trying to do. I will not be comparing Remix with something like Gatsby, as Remix isn’t a Static Site generator. The focus of Remix is server-side rendering, and even though you can use NextJS as an SSG, it has server rendering and does a lot of the same stuff Remix does, but differently.
Initial project setup
I have mixed feelings about the initial setup. Let me carry you through this. In your terminal, you do:
npx create-remix@latest
You see a fancy Remix animation in the console and a menu where you already need to choose where you will deploy your application. Depending on what you pick, you get a project with different configuration files pre-made for the platform you will deploy on. That means if you want to change hosting, you need to think about what needs to change to run it on the other platform. It doesn’t feel great.
On the other hand, it does save you configuration time, it’s easy, and the animated logo in the terminal looks pretty. 🤷♂️
Let’s talk about the most essential thing about Remix.
Routes
Routes are the most significant thing about Remix. To me, it feels like every route is a server route and conveniently has the option to render a client-side UI if you want. Everything in Remix is about routes, and that’s not a surprise if you look at the people that founded Remix.
Every route is a server route and conveniently has the option to render a client-side UI if you want.
Basically, on every route, you have the option to export things. You can render a front-end, get data from somewhere, render an RSS feed, or submit a form based on those things.
Just going over the basics, these are things you can expect from a route:
export {
loader,
meta,
handle,
action,
CatchBoundary,
ErrorBoundary,
};
export default <div />
I’m not going to elaborate on the functionalities of Routes (nested routes, parameters, …), but those are things you want to take a look at once you know the basics.
Loader
Loaders are the backbone of the route, as they do all the heavy lifting. Everything related to data you need in the front-end, you want to return in this function.
The idea behind a loader is that you can do more than return data for a route. You can send complete responses, including cache control headers. When you set cache headers on your response, it means the same route will get the cache if the route has already been fetched.
An example of a loader:
import { json } from 'remix';
import type { LoaderFunction } from 'remix';
export const loader: LoaderFunction = async ({ request, params }) => {
const example = await getExampleData();
return json<LoaderData>(
{ example },
{ status: 200, headers: { 'Cache-Control': 'private, max-age=3600' } }
);
}
Client UI
If loaders are the backbone, the client-UI is the frontal bone. The part you export as default is where you render your front-end. If you have fetched some data inside your loader, you can get it here and render it for your visitors to see.
Getting the data from the loader is easy:
export default function ExamplePage() {
const { example } = useLoaderData<LoaderData>();
return <div>{ example }</div>
}
Everything else in the default export is basic JSX, putting components where you want them, and so on.
At this point, you could make your entire full-stack application with just these two things. Remix makes the communication between the front- and back-end pretty darn easy. You can fetch data from any API, and since it is server-side-rendered, you can fetch data with secret keys without worrying about exposing your secrets.
Remix makes the communication between the front- and back-end pretty darn easy.
Action
Actions are where things get interesting. Action is a server-only function where you can handle actions. It is the same as Loader; the only difference is when it’s called. If a non-GET request is made to your route (POST, PUT, PATCH, DELETE), the action is called before the loaders.
An example action could be submitting a form to subscribe to a newsletter.
We create a simple form like this:
import { Form } from { remix }
export default function ContactFormRoute() {
<Form noValidate method="post">
<h2>Sign up for our newsletter</h2>
<input name="email" type="email" />
<button>Subscribe</button>
</Form>
}
Remix `<Form>` works identical to HTML `<form>`, with a few goodies. If JavaScript is turned off, the form will still work. Remember, the default form only supports GET en POST, so you should probably stick to those if you’re using a form.
And then we create a server-side Action to handle the form submission. When no `action` is provided to a form, it will submit to the page itself.
export const action: ActionFunction = async ({ request }) => {
if (request.method === 'POST') {
const formData = await request.formData()
const email = formData.get('email')
// handle server side validation
if (!email) {
return json({ errors: "no email provided" }, 500)
}
// subscribe login return 500 when things go wrong
return json({ ok: true }, 200)
}
}
To get back to the front-end, you can receive the responses from the action in a few ways, one of them being `useActionData`.
const actionsData = useActionData()
if(actionsData?.ok) return <div>success!</div>
if(actionsData?.errors) return <div>{actionsData?.errors}</div>
When a POST is made to a URL, multiple routes in your route hierarchy will match the URL. Unlike a GET to loaders, where all of them are called to build the UI, only one action is called.
Forms without an action prop (<Form method="post">
) will automatically post to the same route within which they are rendered.
My first thought was. Oh, okay, you can build an entire API like that. Nice. It’s the `/api` folder in NextJS (if you are familiar) but throughout the entire routing folder. AND every route has the option to render a front-end. That’s amazing.
With loaders and front-end rendering, you have all the basics to make an application. Everything else is improving the UX of the application.
Other stuff
Remix doesn’t stop at Loaders and Actions. My first impressions do. If you want to know more about Remix (like nested routes, Error handling and so much more), I would strongly suggest looking at their website: https://www.remix.run.
My opinion about remix.run
I can honestly say that I’m hyped. I am a fan of serverless and JAMStack, so I started skeptically. Remix.run, to me, feels like you are working inside a web framework and not a React framework. You, so happen to be, are working with React on top of it. I have learned about Javascript in my adventure learning Remix, and I’m not done using it. It is promising. A lot of the things make sense.
Thinking about what happens on the server and what doesn’t is something extra to think about, but that might be a good thing.
If you are a fan of magic, I would recommend you to stick to NextJS (or Gatsby for even more magic) or whatever framework you are using at the moment. But if you like bare-boned where you have control over everything, then Remix might be the tool for you.