We've worked on a lot of different types of websites: static sites for credit card companies, social media platforms, learning management systems, content management systems, and ecommerce to name a few. We've also trained hundreds of development teams with our training company, React Training. These teams build websites we all use regularly. Based on our personal development experience and our client's products, we built Remix to be able to handle the dynamic nature of both the front end and the back end of a web project.
The Remix philosophy can be summed up in four points:
You can make your server fast, but you can't control the user's network.
With today's web infrastructure you don't need static files to make your server fast. This very site has a time to first byte that's hard to beat and it's completely fresh. We leveraged distributed systems at the edge instead of static builds. We can fix a typo and the site reflects it within seconds: no rebuilds, no redeploys, not even HTTP caching.
What you can't make fast is the user's network. The only thing you can do is decrease the amount of stuff you send over the network. Less JavaScript, less JSON, less CSS. This is easiest when you have a server that you can move the logic to, and a framework that favors progressive enhancement.
There are a lot of ways Remix helps you send less stuff over the network and we hope to talk about all of them, but for now here's one: fetching a list of records.
Consider the Github Gist API. This payload is 75kb unpacked and 12kb over the network compressed. If you fetch it in the browser you make the user download all of it. It might look like this:
export default function Gists() {
let gists = useSomeFetchWrapper(
"https://api.github.com/gists"
);
if (!gists) {
return <Skeleton />;
}
return (
<ul>
{gists.map(gist => (
<li>
<a href={gist.html_url}>
{gist.description}, {gist.owner.login}
</a>
<ul>
{Object.keys(gist.files).map(key => (
<li>{key}</li>
))}
</ul>
</li>
))}
</ul>
);
}
With Remix, you can filter down the data on the server before sending it to the user:
export async function loader() {
let res = await fetch("https://api.github.com/gists");
let json = await res.json();
return json.map(gist => {
return {
url: gist.html_url,
files: Object.keys(gist.files),
owner: gist.owner.login
};
});
}
export default function Gists() {
let gists = useLoaderData();
return (
<ul>
{gists.map(gist => (
<li>
<a href={gist.url}>
{gist.description}, {gist.owner}
</a>
<ul>
{gist.files.map(key => (
<li>{key}</li>
))}
</ul>
</li>
))}
</ul>
);
}
This drops the payload from 12kB compressed, 75kB total to 1.8kB compressed, 3.8kB total. That's 20x smaller! We also don't need to ship all the skeleton UI because Remix fetches (and can prefetch) this data before the page is rendered. This is just one example of how embracing the server/client model helps us speed up our apps by sending less over the user's network.
These technologies have been around for a long time. They're solid. Remix embraces them completely. Combining HTTP Caching, Remix's focus on URLs for assets, dynamic server rendering, and HTML features like <link rel=prefetch>
, you have all the tools to make your app snappy. Browsers and HTML got really good in the 20+ years we've been using it.
We try to keep the Remix API to a minimum, and instead work with web standards. For example, instead of inventing our own req/res
API, or even using Node's API, Remix (and your Remix apps) work with the Web Fetch API objects. This means as you get good at Remix, you're really just getting good at web standards like Request
, Response
, URLSearchParams
and URL
. All of these are already in your browser, now they're on your server no matter where you deploy to.
When doing data mutations, we augmented HTML forms. When we prefetch data and assets for the next page, we use <link rel="prefetch">
and let the browser deal with all of the complexity of caching a resource. If the browser has an API for a use case, Remix uses it.
While most recent frameworks only have read APIs for data, Remix has both read and write. HTML <form>
has been the staple for data mutations since the 90s, Remix embraces and augments that API. This enables the data layer of a Remix app to function with or without JavaScript on the page.
Adding JavaScript allows Remix to speed up the user experience in two ways on a page transition:
Also, with JavaScript on the page, Remix can provide the developer with APIs to make the UX nicer on page transitions:
Finally, since data mutation is built into Remix, it knows when to refetch data that could have been changed after a mutation, ensuring different parts of your page don’t get out of sync.
The point is not so much to make the app work without JavaScript, it's more about keeping the simpler client/server model. Being able to leave JavaScript at the door is a nice side-effect.
This one is more for us. We've been educators for the 5 years before Remix. Our tagline is Build Better Websites. We also think of it with a little extra on the end: Build Better Websites, Sometimes with Remix. If you get good at Remix, you will accidentally get good at web development in general.
The APIs Remix provides makes it convenient to use the fundamental Browser/HTTP/JavaScript, but those technologies are not hidden from you.
For example, getting CSS on specific layouts in your app is done with a route module method named links
, where you return an array of objects with the values of an HTML <link>
tag. We abstract enough to optimize the performance of your app (they're objects so we can dedupe them, preload them) without hiding the underlying technology. Learn how to prefetch assets in Remix with links
and you've learned how to prefetch assets in any website.
Get good at Remix, get good at the web.