Building Better Websites with Remix

Remix is a new web framework from the creators of React Router that helps you build better, faster websites through a solid understanding of web fundamentals. Remix takes care of the heavy lifting like server rendering, code splitting, prefetching, and navigation and leaves you with the fun part: building something awesome!


Transcript


Intro


Hey, everybody. What is up? My name is Michael Jackson. I am so stoked to be with you here today at React Summit. Thank you so much for tuning into my talk. I'm excited to share with you the stuff that I've been working on today, specifically I want to talk about Remix, which is a web framework that I've been building for the last year or so with my business partner, Ryan Florence. Together, we have been working on React Router for the last five or six years, which is open source software. It's pretty big. It's used a lot of people around the world to build awesome web experiences.

Remix is built on React Router, and so a lot of the awesome stuff that we're building for Remix is actually going to make it back into React Router. But the idea behind Remix is that we think that there should be an awesome experience for developing websites with React Router, with React, websites that are built on web fundamentals, that are just awesome for people as far as accessibility, as far as performance goes, and as far as flexibility and power of the tools that you have at your disposal to build really cool stuff.


DEMO


I'm really excited to share Remix with you today, and I thought I would do a quick demo. I only have 20 minutes, so this is going to go pretty fast, but I wanted to give you a little tour of Remix and some of the stuff we've been working on and show you what it's like to build just a little app with Remix. 

[01:47] I'm actually going to be using our express starter templates. We have a few of these starter templates. We have one right now for express, which you can deploy to any old virtual private server that you want. Lots of different cloud providers will just run node apps. We also have one for Vercel. We also have one for Architect, which is running on AWS Lambda.

We have coming support for Netlify, for Firebase, and CloudFlare Workers. Our plan is with Remix to just support anywhere that you want to deploy a web app, because they all have their different strengths. Remix is a single programming model that can work on any of them.

[02:34] I'm going to start with our express starter. I've got a little app here that is actually... I've been working on it just a little bit. It's basically the express starter, but I've added a few components. I've got Tailwind going on in here. I've got PostCSS going on, and I've got a couple of routes here already.

Let's just start right at the top, right at our entry point. We've got two entry points here in your app folder. This one is the client entry point. This is a regular old ReactDOM.hydrate. The component that we're rendering is our Remix browser component. This is the one that you render when you get to the browser. You have full control over your entry point. If you wanted to do something globally in the browser, you can go ahead and do it here in your client entry point.

[03:21] In your server entry point, this is usually running in node, but this is a function that is basically responsible for rendering the HTML. Remix is fully server rendered React apps, right? You're going to get all that SEO goodness, all of the H... Sending real HTML over the wire. We're not just sending like a shell and then you fill it in later. This is real HTML. We're using a standard rendered string here to render our Remix server component, and there we're returning an HTML page. In this function, you get a request and you return a response.

These objects are not things that we invented. These are just from the web Fetch API. So even though this function runs in node, you get to use the familiar Fetch primitives that you use on the web.

[04:11] Those are our entry points for the client and the server. Let's go ahead and take a look at our routes. You always have the root route. The root route is the single entry point for React router. This app actually gets to render the entire document. We saw earlier that we were rendering into the document. So here we render the HTML element, the head element, the body element. We're rendering meta tags and links. We'll take a look at those in just a second, and then we've got an outlet here. This is the element in React Router V6 that a route uses to render its child routes. The content of the page, just like the meat and cheese in a sandwich, is just going to go right in there in this outlet component. That's going to be one of these routes, either the 404 route or the index routes.

Let's go ahead and take a look at our routes. You'll notice in our index route, we've got a component here. We also have this meta function. Each route gets the chance to define these are my meta tags that I need, meta information about the page. In this case, we have the title and the description for this page. Let's go ahead and make sure our dev server is running.

[05:27] Our dev servers already running here. Let's go ahead and fire that up in the browser and see what we get. We've got our welcome to Remix page. This is just our hello world basically. We're just rendering this little paragraph tag. Let's go ahead and tweak the title, Remix. We're going to be doing an off demo today, because off is something that everybody has to do on the web, and so we're going to show you kind of how you do it in Remix, right? As you tweak your meta tags, we'll automatically update things like the title and stuff for you.

We need to get some styles on here. I told you earlier that we were using some PostCSS. We have PostCSS watcher running and it's just dumping some styles into this directory here. What I'm going to do here is I'm actually going to import a URL for that file from that directory.

[06:25] Let's get some global styles on the page. I am going to have something here called a links function. A links function is going to be used to generate those HTML link elements, and we're going to put those on the page for you. Our links function is going to return a bunch of links that we want on the page. In this case, we want a style sheet. Its URL is that styles URL.

What Remix does is it now knows here's the component that I need to render and here are also some links that I need to render in the page, just like the index route has some meta tags that it needs to render, right? Let's go ahead and refresh this here. We've got some styles. Now let's go ahead and take a look real quick at this network tab. We can see when we refresh the page, we can see we're getting the document. We're also getting the global style sheet that's loading in here.

[07:24] Let's go ahead and grab the styles, we're going to go ahead and put a link tag actually in our index route that is going to import not the global styles, but the index styles. That should be good for this. Let's pop back into Chrome here. Let's take a look. Now we'll see that both the global styles and the index styles are loaded in when we're in the index route. But if we're, for example, at the 404 route, we'll notice we're just getting the global styles. You'll also notice that we got the right HTTP status code for this page. We've got a 404 not found. That's important for when search engines come around and they've hit this URL and they now know that it's a real 404 and not just a 200 that says it's not found. You'll also notice that... The way that we do the CSS is when a route is active, its CSS is also active. Its link tags are on the page. But when that route goes away, it's link tags disappear from the HTML. This helps you avoid conflicting styles between different routes. You can still have your styles use regular CSS. We'll just add and remove the link tags to and from the page when those routes become active or not.

[08:48] Let's head back to our index route. This is kind of boring. Let's add some cool looking components here. Let's grab... I've got a couple of Tailwind components that I'm going to grab here and put on the page. I've got a hero section. I've also not a content section. Content. Instead of saying, "Welcome to Remix," let's go ahead and put the hero on and we'll put the content section on there. Let's head back into the browser. Let's refresh. Okay, now we've got a full blown website. Looks pretty good. I can go home. I'm done for the day. No. Today what we're going to do is we're going to actually... We're going to implement this login button. You'll notice that right now it just goes to a 404 at the sign in route. Let's go ahead and go and make that route a real thing.

I'm going to create a new file here called signin.tsx. We're kind of mimicking URLs with the name of the file, right? If I just have a file here called sign in and it exports a component, we'll call it sign in, and then we'll return a div called sign in, something like that. We'll pop back into our browser and refresh. We'll notice that this component in the sign in TSX actually renders at /signin. It's kind of cool. Let's go and grab a login form. Login form. There we go. Instead of just that div, we're going to return a full-blown login form. Here on our sign in page. Let's go ahead and refresh. All right. We now have a login form at the sign in route.

[10:50] This form doesn't actually do anything yet. We can't really submit it, but we'll notice there are a couple of fields here. We have an email address. We have a password, and then we have this little remember me checkbox. What we're going to do is we're going to go back to our route and we're going to add a handler for the submit. We're going to call this the action, the action function. The action function is going to get your request. This one is actually going to be an async function, because what we're going to do is we're going to read… We're going to read the form data basically out of the form. Awaitrequest.text. You'll recognize that API from the Web Fetch API. The request text is actually going to be URL encoded. We're just going to parse it using URLSearchParams, which is again another Web API. It's actually a node. Let's just console... Well, I know what the form fields are called. There's an email field, data dot get the email. There's also going to be a password in there. Password. There's also going to be a remember me checkbox. I'm going to call that one... So if the checkbox is submitted, its value default is going to be on.

[12:25] Let's just go ahead and log those. And then let's just redirect back to the sign in route when the form is submitted. Let's go ahead and pop back into the browser. Oh, let's make sure we have our console open so we can see the submit as it comes through. I'll just punch in my credentials here, and I'll click the remember check box and I'll say submit. We see in our console log here, we see the email, the pa... Oh, how embarrassing! You can see my password. And the remember check box was checked. Cool.

You noticed there was a post, and then we redirected back to the sign in route, so then there was a subsequent get on the route. What we're going to do now is we're just going to validate these credentials, right? If the email and the password were valid, then we need to log the user in. If they weren't, then we're going to redirect to the sign in page and maybe like show an error or something. 

[13:29] What we need to do now is check these credentials and log the user in if they're valid. Let's do some validation here of the form data. If there's no email, that's an error state. Otherwise, if there's no password, that is likewise another error state. Otherwise, let's say the user is... We have this method called validate credentials that we can give an email and a password to, and that should give us the user. However, if the user is null, then that's another error state. We have a bunch of different error states, but this is the happy path. This means that the credentials were valid. What do we do here?

Well, we're going to use a session to persist the login info across requests. Sessions take advantage of HTTP cookies. Let's get the session and we're going to get it using the session... Remix provides a sessionStorage API. Let's get it from the cookie header that comes out of the request. We've got the session object. Like we said, this is the happy path. Let's go ahead and set the user ID to the user.id, and let's just go ahead and redirect them back to the homepage if they're logged in.

[15:00] Except in this case, we're going to need to add some custom headers, in particular, the set cookie header. And we're going to use the commit session API to get the set cookie header back. So sessions come in via cookies, and then we tell the browser what the new cookie is with the set cookie header. Again, this is just web fundamental stuff. We're not doing anything new here. We don't need that console anymore, or we don't need this console log anymore.

Oh, one other thing that I wanted to remember to do is if they click the remember checkbox, I want to set a max age on this cookie. Let's say like a week. Otherwise, undefined. If they click the remember check box, this cookie will persist for a week. Otherwise, it will just close whenever they close that tab.

[16:00] Let's go ahead and let's test it out. Let's go over here to our form. I'll go ahead and enter my password. We'll sign it in, and it worked!

It looks like we got redirected back to the homepage, although we can't really tell that we're logged in because we saw this login link up in the corner here. Let's head back over to the homepage and let's see if we can get the user here. We're going to use one more function here called the loader function. The loader function is kind of like your action function, except this one is for reads, right? The action is for posts and puts and things like that. This one is for get. You can think about it kind of like your getServerSideProps if you are working in Next.js.

[16:50] We're going to use the request headers. We're going to grab that session. We're going to grab the cookie, and we're just going to return some data here that we're going to use in our routes. If the session has that user ID key, we can go ahead and get the user ID out of the session. We'll get the user ID out of the session. Otherwise, the user is null. The way that we get this data down here in our component is we can use route data. We'll give you a hook in React. This hero section expects a user prop. We'll just go ahead and save that.

We actually have a loader function now that's going to grab our user object to pass it through. We are actually already logged in. Let's go ahead over here to our page and we'll refresh. We can see now we have the user object. We're passing it through to our component here, and we're showing a different message out here. We've also got a form. Let's take a look at this component real quick, the hero section component. And you can see that in here, if we do have a user, we're going to show a form for logging out, right? Hello, username. Here's your button. It's just a standard post to the sign out route. Let's go ahead and we'll add a sign out route here, because right now we just don't have one. Add a sign out TSX. This route isn't actually going to render anything. The component for this route for now we'll just say is empty. What we're really interested in is we want an action in this route, and the action's job is to destroy the session and redirect us back to the homepage.

[18:55] Let's go ahead and grab that session. Same thing that we've done now a few times. We are going to return or redirect back to the homepage. This time the set cookie header is going to be... We're going to destroy that session. The destroy session method, like the commit session method, is responsible for generating a set cookie header. But in this case, instead of persisting the session, this is actually going to destroy it.

Let's head back over here. We'll click our logout button. We're going to hit that action, and we're going to get redirected back to the homepage, except this time no user. User is null. Let's say back over to our sign in route and let's fill in some of these error states. Just like we can use the session to persist the user ID, we can also use the session to persist errors. Let's say if they don't give us an email address, we'll put a flash message. We'll put an error and say, "Please provide your email address." Likewise, if they don't give us a password, we'll say, "Please enter your password." Otherwise, if they give us some bad credentials, we can just say something like invalid user or invalid email and password.

[20:34] In the case of success, we redirect back to the homepage, right? But in the case of an error, we're just going to redirect back to the sign in page. Let's go ahead and we'll persist the cookie here so that these flash messages can make it through. And then on the next get, what we can do is we can use that loader function that we just saw in the root route or in the index route. Let's get our loader in here.

This time in our loader what we're going to do is we're going to get the session and we are going to check for an error message. If there was an error message, we'll do a session dot get the error. And then the data for this route is really just going to be some JSON data. We'll send that error message, and then we'll also send through some headers. In this case, we want to commit the session again, because the case with these flash messages is that they only persist for one request. If you go and you do a get on a key that was set with a flash, you're going to need to go ahead and then commit that session again, because the session content has actually changed.

[22:12] So then we'll get it out in here, so error is used to route data. Let's go ahead and add an error prop to our login form component so that we can go ahead and we can show an error. In our login form component, let's add the error prop. It's going to be an optional string. And let's say if we got an error, we'll just put it in here. We'll put it in some red text. We'll center it. I'll just put it right here.

Let's go back over to the browser and let's test it out and see if it actually works. If we go to login with some junk credentials, we should see the error message here. Nice! We submitted invalid credentials. We set the error in the session. We sent it back to the browser. The browser sent the cookie back with the session. We pulled out the error message and we said, "Hey, go and show it in the login form component," and then we re-rendered the page for the login form. You want to know something that's really cool? Check out the browser console here. We're not actually using any JavaScript on this app at all.

[23:49] This is a completely server rendered app. There is no JavaScript running on the client. If we wanted JavaScript to run on the client, all we would have to do is go back into our root route. See the script's element here? We can just go and dump this thing in the page. Let's go and refresh. Now check it out. Now we've got React is on the page. React Router is on the page. Our component is hydrated.

We actually went through the full hydrate step there, but we didn't need any of that JavaScript to code the flow that we already did, right? That simple login flow. This is what we mean when we talk about getting back to web fundamentals, right? JavaScript should be used to enhance your site, to progressively enhance your site. For those of you who have been around for a while, you're familiar with that term, right? We don't need JavaScript to do everything. We don't need to load up hundreds of kilotes of JavaScript or even megates of JavaScript just for a simple login form. These are the kinds of things that we're interested in.

[25:04] There is so much more that I would love to tell you about Remix, but they only gave me a few minutes in this talk. We would love to tell you more about it over at Remix.run. We're still in a developer preview. We're hoping to launch a beta later this month. If you're interested, you can sign up at Remix.run. Or if you're interested in just following what we're doing, follow us on Twitter @remix_run. If you're interested in following me personally, I am @mjackson on Twitter and GitHub and everywhere else. Thanks again for watching the talk and we hope to see you around.


Questions


[25:39] Nathaniel Okenwa: Thank you so much, Michael. I mean, all of you folks listening at home, go over to the Q and A chat and give Michael a massive round of applause for that amazing talk. I really enjoyed it. Also, I was really impressed the coding that you did. I find it so hard to code when there's a camera or someone recording it. I'm definitely going to ask him for some tips for myself, but we have so many questions coming in from you. Don't stop bringing in your questions. I'm going to shoot them over to Michael. But first, let's see the results of the question that Michael asked you. Michael asked, do you use or have you ever used Remix? And drum roll. The results are that 98% of you haven't used it yet. Well, that is not a surprise considering it's not public just yet, but maybe you might be able to use it soon. I'm pretty sure lots of you are excited about it. But I'm super excited to invite on the stage with me now, Michael. How you doing, Michael?

[26:36] Michael Jackson: Hey, good to be here. Thank you for having me.

[26:39] Nathaniel Okenwa: Thank you so much. I really appreciate the detailed talk that you went in. I'm going to go and watch it again. I can't wait, because it was so nice to see you quickly build up that flow.

[26:49] Michael Jackson: Thank you, man. I've had a lot of practice. We've been doing the React training thing now for about five or six years. And as much as I would like to be somebody who does nice pretty slides, I find that I'm just more comfortable just hanging out some code in my editor. That's always kind of what I default to whenever I give a presentation.

[27:09] Nathaniel Okenwa: No, it's impressive. Now we've got so many questions, so I'm going to just jump straight in. The first question we have from Vasily says, "What would you say is the biggest reason that they should choose to use Remix instead of Next.js or Gats or some other service side supporting framework?"

[27:28] Michael Jackson: The whole idea behind Remix is that you should be able to use your favorite web technologies or your favorite stack. It's not super opinionated. For example, we put Remix and we will take it and we'll run it on AWS Lambda, or we'll take Remix and we'll run it on CloudFlare Workers, or we'll just run it in an Express app. You know, you can use Tailwind with it. You can use CSS and JS with it, right? It's super flexible. Of course, the whole thing is built on top of React Router, which if you're familiar with the concept of nested routing is actually a really powerful concept which lets us put only the code that you need on the page, so only the JavaScript, only the styles, only the data for that particular route, or the set of active routes are on the page at once. It's a lot of fun. Honestly, it's more kind of like steeped in web fundamentals, right? Things like support for the back button, support for forms for data mutations, things like that. Instead of loading up a bunch of JavaScript to do your data mutations, just use an HTML form, stuff like that. We've had a lot of fun building it and the feedback that we've got from people so far is that they're having a lot of fun too. We've got a great Discord server that's running. We're there all the time. We're answering questions about it. Yeah, you should definitely come check it out if you're interested in building awesome sites where we're having a lot of fun with this.

[29:05] Nathaniel Okenwa: Awesome! I know we've got so many questions, so I'm going to fly through them, but I love what you said about building on where people can use the web technologies that they already love. I think that's so good to be able to cater for all of those people. One question which I really like is from George B, "Do you have some examples of specific use cases where Remix really shines?"

[29:26] Michael Jackson: Yeah. I think it really shines in the case where you... Well, so like the styling case, for example, right? CSS is default global, right? You have one namespace where you have to put all your class names, all your IDs, et cetera. Typically, what happens when people want to deploy their CSS or their styling is they have to go and... They'll take their CSS class names and they'll sort of mangle them just to make sure that they're all unique, that they don't conflict with CSS class names from other components or other things that may be on the page. The truth is, they just don't know what's going to be on the page, so mangle them all and then there won't be any conflicts. This is a case where Remix really, really shines and the nested routes that I was telling you about because with React Router, you actually have two or three routes on the page, and we will automatically add and remove the CSS to and from the page for just the routes that are active on the page. You could have two sibling routes that have identical class names, but that's fine because they're never going to be on the page at the same time.  And the way, we're not going to mangle them. They just won't be there, so you won't have the conflicts. That's one of the cases I think where Remix really, really shines is in avoiding some of the hoops that we've been jumping through with CSS.

[30:59] Nathaniel Okenwa: Definitely. I do everything I can to avoid hoops. We are running out of time, and there are so many questions coming up. But folks, those of you... If we don't answer your question, I want to let you know that you can join Michael in his discussion room on Remix with Kent C. Dodds, and the discussion group will be on the Discord. So go and find that channel and the link is on the timeline on the website. But right before we go, let's do a quick fire of some more of these questions. Just three quick fire ones. Are we going back to the PHP era? Yes or no?

[31:34] Michael Jackson: Oh, absolutely. I hope so. The thing is PHP was used to generate HTML. HTML is what we care about, right? That is the language of the web. That is the language that browsers understand. Yeah, it's nice to client side render your stuff, and we built React Router which is a client rendered a router framework. We absolutely think that you can enhance the web with client side rendered stuff, but the core of it is HTML. You can think about Remix as being a framework for creating quality HTML driven documents, much like PHP was.

[32:13] Nathaniel Okenwa: Yeah, quality HTML. That's where we always want to end up with as developers. Thank you so much. I really appreciate you coming on today.

Michael Jackson: Hey, thanks, Nathaniel. It's been a pleasure.

Nathaniel Okenwa: See you! Bye!

Michael Jackson
33 min

Check out more articles and videos

Workshops on related topic