Coming up to three years ago I made the shift away from web development as a hobby to web development as a job. Part of finding the confidence to do that was the learning in public I did here, on dev, so in that same vein I'm going to try my hand at learning remix.
Before I get started I should say I'm a front end developer with very minor back end experience. I've used NodeJS before and I've dabbled with PHP and Perl. Also I know NextJS is quite popular and may have even been a better choice to start with but I decided to see what Remix had to offer. I may look at NextJS in the future we'll see how this goes.
I'm going to be following the Blog tutorial on the Remix website, at least to start with. If I get to a place where I feel things are making sense I may stop following it and see where I end up. Now, without further ado, here we go.
The setup
Well first things first let’s make a place for us to do the work. I made a new folder called remix-server
, though the name isn't important, and opened VSCode. In the terminal I entered npx create-remix@latest
and followed the instructions.
Need to install the following packages:
create-remix@latest
Ok to proceed? (y)
R E M I X
💿 Welcome to Remix! Let’s get you set up with a new project.
? Where would you like to create your app? .
? Where do you want to deploy? Choose Remix if you’re unsure, it’s easy to change deployment targets. Remix App Server
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes
> postinstall
> remix setup node
Successfully setup Remix for node.
When it asked where I wanted to create the app I just used .
as that means here, had I wanted to make a new directory I could have written the directory name there like .\my-dir
.
You may also have noticed I'm using Typescript rather than JavaScript, this is because I'm learning Typescript anyway but if you want to use JS that's fine most of what we write will be transferable, I'm sure.
Once NPM had done its thing I was able to run npm run dev
and look at the server.
Making routes
If we look a the files that have been created you'll noticed we have a folder called app
and inside it another folder called routes
in which there is a single file called index.tsx
, a tsx
file is a typescript react file you might see jsx
which is the js equivalent. I had a look inside the index file and saw it was a normal looking react file that contained the contents of the demo page we'd just made.
Reading ahead in the tutorial we see it wants us to modify the index file and make a new route so let's do that but also let's deviate from the script just enough to make sure we know what's going on.
I plan to amend the index file to contain a Link
as it says to do in the tutorial but then I will make a new directory called test and inside it I will put a file called index and another called inner. I posit I will then be able to got to localhost:3000
, localhost:3000/test
and localhost:3000/test/inner
to see my 3 files.
/* base level index */
import { Link } from "remix";
export default function Index() {
return (
<>
<h1>This is a test</h1>
<Link to="/test">Test page</Link>
</>
);
}
/* Index file inside '/test' */
import { Link } from "remix";
export default function TestIndex() {
return (
<>
<h1>This is a test</h1>
<Link to="/test/inner">Test inner</Link>
</>
);
}
/* File called inner inside '/test' */
export default function Inner() {
return (
<>
<h1>You found me!!</h1>
</>
);
}
And what do you know it works. It seems any directory inside the routes folder becomes a route, if an index file is inside the route it is served when you go to the route directly and any other files can be reached by typing their name after the route in the URL. I'm sure there will be more complexity than this further down the road but that seems to be a good enough understanding for now.
Let's get on with the tutorial.
Getting data
This section feels a little messy to me, it starts by having you put all your code in a single file then tells you this isn't best practice and has you refactor it. Also, in the typescript version, it has you use type when a interface works perfectly well. I'm going to tell you what I did differently I don't think it will make a massive difference in the end but I feel it's less confusing this way.
useLoaderData hook
Firstly we need to use a hook called useLoaderData
we import this from remix
just like we did for link. This also requires us to have a function exported from the file we're using useLoaderData
in called loader. It's best practice to make this function async
as that allows us to wait for data to load. The loader function should return our data in the format we want to ue it in our react file.
import { useLoaderData } from "remix";
export const loader = async () => {
return 'hello world';
};
export default function Posts() {
const loadedData = useLoaderData();
return (
<>
<h1>Posts!</h1>
{loadedData}
</>
);
}
This above snippet would print 'hello world' as loadedData
would become what the loader
function returns.
Now if we want to get more complex data from our loader
it's a good idea to make a new file that contains the function, then import that function into our react file and use it inside the loader
function. As we don't want this new file to have a route let's go back up to the app
level and make a new folder called 'loaders' in here we'll make a file called posts.ts
.
export interface Post {
slug: string;
title: string;
}
export const getPosts = (): Post[] => {
return [
{
slug: "my-first-post",
title: "My First Post",
},
{
slug: "90s-mixtape",
title: "A Mixtape I Made Just For You",
},
];
};
This file contains an interface that describes the data that getPosts
returns. We also have a function called getPosts
that simply returns the 'data' we want to get. In the future this could contain some database calls or something but let's keep it simple for now. Both the interface and function are exported so we can use that back in our posts index file.
import { Link, useLoaderData } from "remix";
import { getPosts, Post } from "~/loaders/post";
export const loader = async () => {
return getPosts();
};
export default function Posts() {
const posts = useLoaderData<Post[]>();
return (
<>
<h1>Posts!</h1>
{posts.map((post) => (
<li key={post.slug}>
<Link to={post.slug}>{post.title}</Link>
</li>
))}
</>
);
}
As you can see we're importing the interface and the function. The interface lets us modify the useLoaderData
function which allows us to have auto complete in our IDE by saying the posts
const is and array of Post
s as described by the interface.
Dynamic routes
To add a dynamic route you make a new file in routes directory. The files has to start with a $
but the rest of the name can be anything you like. In the example given by remix they use slug
so we'll do the same.
Something magical happens when you do this. The loader function from before can see the URL you've entered and do something with it but let's take a step back and understand what is going on here.
It turns out the loader function is always past an object that we can use. That object contains a request
object, a context
, which was undefined for me, and a params
object. The request is the full request the server is receiving including the full URL, the method, GET in this case, and even a query which might come in handy later. But now we have a simple understanding of what the loader function can do let's carry on.
The part of the URL we care about is stored in the params part of loaders params and is called whatever the file is called minus the $
, slug
in our case.
export const loader: LoaderFunction = async ({params}) => {
return params.slug;
};
LoaderFunction
is an type we imported from remix to keep typescript happy.
Loading data
We can use normal NodeJS things like fs to load in files from the file system. The tutorial has us create some markdown files out site of the app directory that we can load in.
Using fs
we can get a list of all the filenames also we can load the post titles, which are inside the md, using parseFrontMatter
. This data can be structured to replace the old static array of posts.
Now we need a new function that can take our slugs, from the dynamic route, and open the file to display the md
as html. Once again we use fs
to load the file then we use marked
to convert the md
to html. We call this function getPost
and once we've imported it into our $slug
file we're there.
Recapping
To recap we've,
- started a new project using npm
- made some custom routes
- loaded some data to construct a dynamic list of content
- read an
md
file and converted it to html - displayed that html in a dynamic route
That both feels like a lot but also hasn't felt like too much. We're only half way through the tutorial but this post is getting long so it feels like a good place to get off for now.
Thank you for reading it really means a lot. Please feel free to leave a comment even if it's to tell me what I've done wrong or what I can improve.
If you'd like to connect with me outside of Dev here are my twitter and linkedin come say hi 😊.