Today the Remix Run Supporter Preview finally launched. Remix is a new React framework.
One of the thing that excites me the most about Remix is the team behind it. Michael Jackson and Ryan Florence are some of the most knowledgeable engineers in the React community and have built a lot of things (like React Router, UNPKG and Reach UI) that I have used countless times in my career personally, at AWS, and when working with customers when I was consulting.
Another thing that excites me is the innovation that has happened over the past few years since React was first released and how they have built upon those ideas in Remix.
In this quick post I'll show you how Remix works:
- Creating a new project
- Setting credentials
- Configuring routing
- Adding dynamic routing
- Dynamic data fetching
- Dynamic data fetching using route parameters
- Using the global Remix config
You can also check out my video walkthrough of how Remix works here
About Remix
Pricing
Remix is not free. To use Remix, you need to buy either a indie ($250 per year) or enterprise ($1,000 per year) license. There are a lot of opinions about this. I think there's a good argument on both sides, so here's mine.
I've personally paid more than $250 for 1 hour of training when I was getting into consulting, so for an entire year of support from a team I can trust for a framework that makes it easier for me to build apps – I think it's worth it. You might also look at frameworks like Next.js that are free and decide it's not worth it. Both opinions are totally valid IMO.
At the end of the day I just want to be able to build high quality apps and do so as quickly and efficiently as possible without compromising quality, and because time is money I often invest in tools that can make me better and faster (especially those where I can leverage my existing skillset).
SSR
The entire idea behind Remix is that everything is SSR. It also has a lot lower level of an API than something like Next.js, exposing the entire Request object and allowing you to modify things like headers before rendering the page. I still don't know all of it's capabilities, but at first glance I see it as a closer to the metal / more configurable version of Next.js but without SSG, and there are some benefits to nested routes that I honestly haven't quite explored yet, but it feels interesting.
Routing
One other large difference between Remix and other frameworks (like Next.js) is how the routing works. Nested routes and params are supported in Remix, and "are a critical idea to understand in Remix" (according to the docs).
Using an Outlet
from React Router Dom, you can build out a hierarchy of nested routes with a pretty simple to use API:
import React from "react";
import { Link, Outlet } from "react-router-dom";
import { useRouteData } from "@remix-run/react";
export default function Team() {
let data = useRouteData();
return (
<div>
<h2>Team</h2>
<ul>
{data.map((member) => (
<li key={member.id}>
<Link to={member.login}>{member.login}</Link>
</li>
))}
</ul>
<hr />
<Outlet />
</div>
);
}
When you navigate using a Link
, the Outlet will render the new content from the navigated route.
HTTP Caching
Remix has this idea of loaders that enable you to not just return data for a route, but send full responses, which includes sending cache control headers. By having simple apis to set headers for loaders and routes, you can easily take advantage of browsers (and CDNs) built in cache capabilities).
For instance, if you set cache headers on your responses, when the user visits the same route, it wont' even fetch the data, it'll use the cache. And if you put a CDN in front of your server, the server will rarely actually handle the requests because the CDN will have it cached
Code
Enough of the explanation, let's look at some code.
Creating a project
When you purchase a subscription for a Remix license you are given access to their dashboard. In this dashboard, you can view your license details, documentation, and billing information.
This is a much different experience than most frameworks I've used that are simply open source, while this is all hidden behind the paywall.
From this dashboard you have everything you need to get going, including a quick start tutorial.
To get started, they recommend you clone an open source starter project that uses express as the server:
$ git clone git@github.com:remix-run/starter-express.git my-remix-app
Out of the box they support Firebase to deploy using a single command:
firebase deploy
In the future they plan to support deployment to these different cloud service providers as well:
- Firebase
- Vercel
- AWS Amplify
- Architect
- Azure
- Netlify
Restricting access
You may be wondering how they limit access to only paying customers. The way they do this is that, to install the latest node-modules
necessary for the app to run, you have to configure an .npmrc file that looks something like this to include your secret key:
//npm.remix.run/:_authToken=your-unique-token
# This line tells npm where to find @remix-run packages.
@remix-run:registry=https://npm.remix.run
Once this is configured you can install the dependencies using npm or yarn.
Project structure
Here is a look at the Remix-specific project configuration
remix-app
└───app
│ │ App.tsx
│ │ entry-browser.tsx
│ │ entry-server.tsx
│ │ global.css
│ │ tsconfig.json
│ └───routes
│ │ index.js
│ │ 404.js
│ │ 500.js
│ │ index.css
└───config
│ │ shared-tsconfig.json
└───loaders
│ │ global.ts
│ │ tsconfig.json
│
└───public
│ │ favicon.ico
│
└───.npmrc
│
└───remix.config.js
│
└───server.js
The entry-point is App.tsx, and looks something like this:
import React from "react";
import { Meta, Scripts, Styles, Routes, useGlobalData } from "@remix-run/react";
export default function App() {
let data = useGlobalData();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<Meta />
<Styles />
</head>
<body>
<Routes />
<Scripts />
<footer>
<p>This page was rendered at {data.date.toLocaleString()}</p>
</footer>
</body>
</html>
);
}
One interesting thing is this line:
let data = useGlobalData();
In loaders/global.ts you can configure global values, variables (both static and dynamic), or anything you need to load from the server in order to render your base layout. For instance, let's say we wanted to define the app version here and use it throughout our app, we can define this here:
import type { DataLoader } from "@remix-run/core";
let loader: DataLoader = async () => {
return {
date: new Date(),
version: "V2.0"
};
};
export = loader;
And then use it like this:
let data = useGlobalData();
const version = data.version;
Routing
From the docs: Routes in remix can be defined two ways: conventionally inside of the "app/routes" folder or manually with remix.config.routes.
So if I create a file called routes/contact.js, it will be it will be available at http://myapp/contact.
As mentioned though, you can also define them in remix.config.js.
From the code comments in remix.config.js:
A hook for defining custom routes based on your own file
conventions. This is not required, but may be useful if
you have custom/advanced routing requirements.
Here's the example provided by the boilerplate:
routes(defineRoutes) {
return defineRoutes(route => {
route(
// The URL path for this route.
"/pages/one",
// The path to this route's component file, relative to `appDirectory`.
"pages/one.tsx",
// Options:
{
// The path to this route's data loader, relative to `loadersDirectory`.
loader: "...",
// The path to this route's styles file, relative to `appDirectory`.
styles: "..."
}
);
});
},
In doing this, you can define custom route configurations if you want or need to bypass the opinions of Remix.
Data loading
One of the most interesting and powerful things about Remix is how it loads data.
The approach combines routes with loaders to enable dynamic data fetching.
From the docs: Page data in Remix comes from files called "route data loaders". These loaders are only ever run server side so you can use whatever node modules you need to load data, including a direct connection to your database.
If you name a loader the same as a route, Remix will automatically call it before rendering, and make that data available in your route.
Let's take a look at how this works.
Let's say I create a route and page at routes/people.ts that looks something like this:
// routes/people.ts
import React, { useState, useEffect } from "react";
export default function People() {
return (
<div>
<h2>Star Wars Characters</h2>
// todo, fetch & map over star wars characters from API
</div>
);
}
When this route is rendered, I want to fetch the data for the array of people and make it available in the component.
To do this, we can create a new file in the loaders/routes directory called people.ts with the following code:
// loaders/routes/people.ts
module.exports = () => {
return fetch(`https://swapi.dev/api/people/`);
};
You can now use the useRouteData
API from Remix to get access to this data in the route:
// routes/people.ts
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useRouteData } from "@remix-run/react";
export default function People() {
const data = useRouteData()
return (
<div>
<h2>Star Wars Characters</h2>
{
data.results.map((result, index) => (
<div key={index}>
<Link to={`/person/${index + 1}`}>
<h1>{result.name}</h1>
</Link>
</div>
))
}
</div>
);
}
The big question
Why didn't we have to await the fetch and then await the res.json? Because Remix awaits your loader, and fetch resolves to response, and Remix is expecting exactly that type of object.
From the docs: You might be scratching your head at that last bit. Why didn't we have to unwrap the fetch response with the usual await res.json()?
If you've been around the Node.js world for a while you'll recognize that there are many different versions of "request" and "response". The express API req, res is probably the most ubiquitous, but wherever you go it's always a little different.
When browsers shipped the Fetch API, they didn't just create a spec for window.fetch, but they also created a spec for what a fetch sends and returns, Request, Headers, and Response. You can read about these APIs on MDN.
Instead of coming up with our own API, we built Remix on top of the Web Fetch API.
Adding caching
Let's have a look at how me might implement the caching mechanism I mentioned at the start of this post.
We can make the following update:
const { json } = require("@remix-run/loader");
let res = await fetch(swapi);
let data = await res.json();
return json(data, { headers: { "cache-control": "max-age=3600"}})
For the next hour, the browser won't request that resource again, and for the next visitors, the CDN won't either.
Combining dynamic routes, route params, and data fetching
How does this work for Dynamic routes? For example, what if I want to drill down into a route like /person/#person-id and fetch the data?
Here's how that works. We need two new files: One for the route and one for the loader.
First, we'd create a loader at loaders/person/$id.js that looks something like this:
// loaders/person/$id.js
module.exports = ({ params }) => {
return fetch(`https://swapi.dev/api/people/${params.id}`)
};
Next we'd create the route, something like routes/person/$id.js. Remix will parse the params from the url (the $id portion of person/$id) and passes them to the loader.
// routes/person/$id.js
import React from "react";
import { useRouteData } from "@remix-run/react";
export default function Person() {
const user = useRouteData()
return (
<div>
<h2>{user.name}</h2>
<h3>Homeworld - { user.homeworld }</h3>
<p>Height - {user.height}</p>
</div>
);
}
The data fetched from the API will now be available using useRouteData
.
Up and Running with Remix.run video
Conclusion
Overall I'm really enjoying using Remix. It's an entirely new approach to building hybrid React web applications that is built upon the advancements we've seen in both modern and legacy web technologies over the years.
I expect to see Remix continue to get better over time, but am unsure how far or fast it will take off considering it is not OSS.
I applaud Ryan and Michael for trying a new payment model, and look forward to seeing how it turns out. The challenge of monetizing OSS has not completely been solved yet, so any new approaches in doing so lay the foundation for anyone looking to make their open source work profitable and maintainable now and in the future.
Also check out my post going a little deeper into the differences between Remix and Next.js.
Top comments (25)
Nice writeup, Nader!
One thing I'm still confused about, what's the difference between this and Next.js, or other up-and-coming meta React frameworks like RedwoodJS or Blitz.js? I've read a few pieces on Remix and can't for the life of me figure out what the value proposition is over these free and open source alternatives.
The most striking difference between Remix and Next is that Remix supports Nested Routes.
In Next.js each route is totally independent so when you transition to a route all previous components get unmounted and the next page's components get mounted.
Suppose you render a sidebar in /dashboard. When you go to /dashboard/admin that sidebar won't exist now. To sidestep this issue you'd make layout components and reuse them in every route. However, since the component gets unmounted during the transition they will lose their internal state. If the sidebar had a search field and the user had entered some text in it, that text will be lost on page transition.
In Remix, since it supports nested routes you can choose which part of the dashboard page remains for /dashboard only and which part remains the same for /dashboard and /dashboard/admin. This is a lot less wasteful IMO.
Another benefit of nested routes is that you can get granular data from your loaders. If a user goes from /dashboard to /dashboard/admin, the data of /dashboard need not sent again.
This is just what I've gathered from reading the newsletter. I expect a lot of innovations on top of nested routes.
Regarding comparison with Blitz- Blitz is a framework built on top of Next.js and it owns even more of your stack. Its aim is to give you Ruby on Rails like developer productivity. It includes doing database queries and other superb stuff out of the box. I expect something like it can be built on top of Remix too.
I'm not sure if that's entirety true. Next.js SSR on internal routing would not request a new page/HTML, instead it requests the JSON output of the getServerSideProps of the new page, and then Next.js on the client end, hydrates your new page using that JSON.
Hi Ahmad!
You're totally right in what you're saying. However, the intention of my comment had nothing to do with requesting new HTML. It might have come across that way. Can you point me to the line which is false in my comment?
I'm not trying to point where you're false or not. 😂
I think you believe that the entire page render happens again and again with Next.js which I believe is not true.
SSR DIRECT PAGE REQUEST
When you request this page directly (by entering the page URL in the browser), the page is pre-rendered by the server and sent to the client.
STEP #1: Server pre-renders the page with dynamic data
STEP #2: Client shows the pre-rendered page
SSR CLIENT-SIDE PAGE REQUEST
When you request this page on the client-side page transitions through next/link or next/router (by clicking an internal link on a page)
STEP #1: Next.js sends an API request to the server
STEP #2: Server runs the getServerSideProps
STEP #3: Server returns resulting JSON as a response
STEP #4: Next.js uses this JSON response to render the page
Now mix this with SSG where parts of your page are static. Like you mentioned the sidebar. That can be completely static with no JavaScript at all. Hence creating a hybrid SSR, CSR, SSG page with Next.js can improve perf a lot here.
I think by the word 'render' we're talking different things. By render I just mean re-render of React component. It has nothing to do with going to server. I'm just saying components of dashboard page won't render on dashboard/about by default so you need Layout components. But that leads to an issue of loosing internal state even though its a client side transition.
I think Adam has explained the problem really well.
adamwathan.me/2019/10/17/persisten...
You can handle that with React memorization to a point. The concurrent mode would make things much better. But hey, I get what you mean.
But the point being made is that the way components are NOT reused in every route, and sharing the data between components when it comes to nested routing of remix is way more convenient. Static Rendering/SSR is an entirely different topic and it has to do nothing with routing ain't it?
SSR has a lot to do with routing when you use Next.js. There's a difference in how routing works when you have an SSR page with Next.js.
How does SSR have anything to do with routing? I think the first user that replied to my comment made a fair point, it's something that I've struggled with personally, and afaik, that has nothing to do with whether or not a page is server-side rendered. Afaik very page component in NextJS gets unmounted before the next page component is mounted, whether or not the page is SSR'd.
Thanks for sharing your early evaluation of the product Nader. It’s interesting that they expose the request and response and the route story is pretty interesting using parameters as part of the file name. I wonder if they will delve into static site generation (SSG) to be embraced in the Jam Stack ecosystem. I saw Jay Phelps talk yesterday about ESR (Edge Slice Rerendering), so I’ve got that on the brain too. 🧠
Looking forward to your next post!
I need to check that talk out!! Thanks for sharing.
I've heard about Remix, but never tried it out and I probably never will either given the fact that it's paid-only. Maybe that will only be the case till they're production-ready? I really don't know.
If they plan on doing this post-production, it will just introduce segregation to a community that is already struggling with finding good solutions to problems and hence fail as a solution.
The problems that Remix attempts to solve are already solved by frameworks like NextJS (can't say for sure because I just read their docs) and NextJS, in particular, has a very bright future (attended the NextJS conf and it was brilliant). There are plenty of free alternative solutions to the problems Remix has outlined if frameworks like NextJS are overkill.
What would be great is if they focused more on their value proposition or how they plan to solve problems better/differently compared to existing solutions. That way it's easier for us as devs to make a decision as to whether or not Remix is something we should be excited about.
I explain the difference between Remix and Next here dev.to/itaditya/comment/17blp
Do you think going back to SSR is a step back from the JAMStack, or do you think it's simply for other kind of applications?
I think that Jamstack is starting to become an outdated term, especially with the advancements you're seeing that move out of the static world and into the hybrid world (though I'm sure the people making $ off of Jamstack will probably evolve their "definition" to suit their needs). I think that something like Remix would be ideal as a replacement for something like a traditionally server-rendered site that has highly dynamic data and a lot of routes. So maybe for instance a replacement for a rails, python, or PHP site. I don't think it really competes with Next.js as it doesn't offer SSG (this is of course just my take).
What do you mean by outdated?
I had the impression that JAMStack always was hybrid (CSR+SSG).
I think that most people consider only SSG to be Jamstack (but some consider Next.js to also be, even with the hyvbrid approach), and have read stuff from people at Netlify (like this post) that drive that message home, but I think they'll be evolving that definition IMO as the modern web continues to evolve.
I see.
Thanks for your insights :)
I could not agree more :).
By the way, what do you think of Next.js 10?
Regardless of how you feel about the pricing, it really is one hell of a bold approach to monetizing OSS. I have a feeling it will take off.
Thank you , Nadar. This is a good start for the developers.
I want to try my hands on it...though 250 USD just to try would be a huge cost.
Excellent review, Nader. I'm actually knee deep in the documentation already. Looks interesting. 👌
Thanks for the post, this helps a lot in understanding the base idea and concept of remix. 🎉
Great to hear!!