I am trying out Remix. This readme contains my account of the experience. The rest of the readme below a horizontal line is the original generated readme.
The first thing to note is that Remix uses a source generator a scaffold a new
project. This is similar to CRA. I don't like this way of starting off projects,
but it seems to be the new thing now. At least as far as source generators go,
the Remix one is decent; it allows generating the project in the directory it
was run from (by specifying .
), which some others do not.
It seemed as though Remix was going to host the application for me, I chose the
Remix App Server option in the generator. I tried to figure out how that works,
but the generated readme does not cover this and the package.json
scripts do
not have any deployment script either.
It seems that running npm run build
gives you a production-ready static file
server friendly output, which is expected, but the generator really made it seem
like there was going to be another script for deploying to a Remix hosted site.
I went to the Deployment guide and it said to read the readme… Not helpful as
the readme doesn't cover this in an understandable way. I guess the output of
npm run build
drops two directories, build
and public/build
, but it is not
immediately clear how to run these on my own server. Do I use a Remix command in
the same directory where I put them? Or do I just run a static file server? I'll
try to find out later.
But still, I did not choose to host on a static file server, I chose Remix App Server in the prompt. What is that? Deployment guide did not answer this, it pointed me to the readme which did not answer it and it all starts to feel like the "mom told me to ask dad who told me to ask mom" meme.
Onwards past this deployment hurdle. Let's run locally first and see what's up.
npm start
seems to be a command to run the production build, if I'm guessing
correctly. This is an unfortunate choice in my opinion, but I'll be able to live
with npm run dev
.
This command runs the development build and it binds to all interfaces. This is cool, because it in theory means I can test the app on my phone on my home wi-fi network right away. In practice, my phone couldn't reach the page, but this is most certainly down to my shitty router, so I can't blame Remix for that.
That scaffolded basic app is very spartan, but I like that. No frivolous styles to remove right out of the gate.
I am working on adding a /posts
route to my app now. The tutorial suggests
using a Link
component which is at this point IMO not needed. It renders out
the same as using simple a[href]
. I wish people would default to this and only
use Link
when it brings a benefit. Remix seems to be all about using HTML
first and augmenting it, so why not do that here?
The suggested implementation of the /posts
handler, the posts/index.tsx
component, uses export default function
. This is my preferred way and it is
nice to see in the sea of no-default-exports cargo culting.
The data loading thing up next is interesting. I am not a fan of the named
export of the loader
. I guess this is a hardcoded name? Renaming it breaks the
data loading, so I suppose that answers that.
It's cool how a form of server-side rendering is built into Remix. Refreshing
the page with the data loader wired up prints the console log statement on both
the server and the client. We can see why that is when we check the source code
of the page as returned from the server (Firefox right click > View Page Source)
and in it, the routeData
field on the global window.__remixContext
object.
Serializing this data into the HTML is the server hit, hydrating the React app
is the client hit. Both call the render
method of the component.
I say this is a form of SSR, not SSR, because I have not yet verified actually rendering the data into the component will render HTML on the server, not just serialize a bunch of JS useless for SEO. Let's see about that next.
I followed the next part of the tutorial and it showed how to render the data and add TypeScript types nicely. Indeed the SSR works as expected and the result would be very SEO-friendly, that's nice to see.
The app/post.ts
thing is not my style, I will reserve judgement for now to see
how it feels to me after having used it for a bit. So far so good, the only nit
is that at this point in the tutorial, the loader
has been too async
-happy
and now with the call to getPosts
also await
-happy. Similarly to the
premature use of Link
, let's hold off on these until they are actually needed.
Oh and what is up with the ~
imports? Is this a Webpack thing being adopted by
Remix? Or is that a part of ESM I didn't know about? It really doesn't look like
the latter and I think we can all agree we don't need more of the former. Bummer
this is. I am not going to like this I don't think. :(
Onto Pulling from a data source. I am pleased to learn that VS Code will do syntax highlighting for MarkDown frontmatter. I don't use it a whole lot, so I had no idea, but I might start.
tiny-invariant
is also interesting, although in general I don't like the idea
of using packages for runtime checks. TypeScript should have a mode to emit
runtime checks with some supporting syntax if necessary, but ideally just based
on the explicit or inferred types. Oh well, here we are.
Ah, finally a reason to make the loader
async/await! I'm not psyched about the
tutorial introducing TypeScript errors and then going on to fix them. I guess it
is good for learning, but to me it just gives me a pause thinking there is an
error in the tutorial only to learn I could have saved my typing and copy-pasted
the next step right away.
I like the speed of the Remix local server. The application is proper snappy.
The dynamic route arguments seem okay. The separation of the logic dealing with
the FS into post.ts
makes more sense now, but I still don't think I will like
or indeed use this pattern myself. I will stick to my function per file habit.
Looking at the import list of post.ts
with the addition of marked
hints at
why I prefer this. A single list of a bunch of dependencies, many of which only
a sole function will use.
Alright, overall so far so good, Remix seems nice and simple. Let's advance. Creating Blog Posts. As a side-note, who decided it was a good idea to make the headings in the tutorial not selectable? It's a joy a go into the Dev Tools just to copy-paste the heading text for the purpose of the MarkDown link here.
On no, styles. I was praising Remix for not including too much right off the bat
in the source code generator. I would have added that I don't think separating
the styles sheet files that are 1:1 with the component files in a different
directory than the components themselves doesn't seem like a great idea. I know
they don't technically need to be 1:1 and this pattern is more general, so I see
the appeal, but really, for components, it's in my opinion best to keep the code
and style files side-by-side. Also, importing the style only to re-export it in
the links
special-case export? I already don't like loader
, links
is not
going to fare better. That being said though, this is not worse than Next's
Head
element and stuff like that. Of course nothing beats just adding a
style
element to your component, but I do understand that the idea here is to
place all of the stylesheets into the head
element and do not have duplication
in the body
in case the component appears multiple times. Anyway, styles are
meh right now.
This idea with nested routes is pretty cool. This is what makes Remix stand out,
finally. I am not sure yet if I'll like it, but I can already respect it, even
if I have to supress some ASP .NET master page memories. Of course, the nested
routes use client-side navigation (now there's a reason to use Link
over a
)
as confirmed by watching the network log with persistence enabled to make sure
the Remix dev server wasn't just super duper fast.
This next whole section about accepting the form submission and binding the action data to the component's backend was impossible for me to read due to the TypeScript errors I mentioned before. I am going to go fixing them before I continue reading, or at least skip ahead and copy-paste in fixes for them without having read the stuff in-between. Now all that was achieved is that I have skipped over a part of the tutorial. The TypeScript errors are not the golden kernel at the core of the tutorial, there's no need to withold the fixes for them for the big reveal. They are just types. Just provide the working snippet and then explain stuff.
Anyway, I think I get it just based on the copy-paste, we have another hardcoded
named export that gets called when we submit a form, we add another method to
post.ts
and we add more runtime checks that make the code ugly.
It almost worked on the first try, I just forgot to add an import of json
from
Remix to post.ts
for createPost
which beautifully shows how this class is no
service or anything like that if it works with concrete types for HTML responses
like that. There's no benefit to be had from centralizing these CRUD functions
in a single file like this.
Oh and the post creation logic doesn't add main headers to the post files so now
we have the previous posts with explicit header and new posts with no header
which we could pull from the metadata and display in an h1
but then the old
posts would have a double header. Another nit, but just why?
Next step, useTransition
. BTW the diffs in the tutorial are weird, they don't
show all changed lines, so copy-pasting just the changes doesn't work. This can
be seen with the post Submit/Submitting button. Changing it to display a label
appropriate to the current transition state shows us just changing the content
with the button tags already on their own lines, but they weren't.
The suggested next steps is to implement /admin/edit
, but I'd much rather see
the Remix-suggested way to gate the admin behind a login screen. Hardcoded would
be fine. That throws a wrench into SSR because the data cannot be returned until
logged in, so that would make it more interesting.
Maybe I'll learn this in the Jokes app?
From your terminal:
npm run dev
This starts your app in development mode, rebuilding assets on file changes.
First, build your app for production:
npm run build
Then run the app in production mode:
npm start
Now you'll need to pick a host to deploy it to.
If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
Make sure to deploy the output of remix build
build/
public/build/
When you ran npx create-remix@latest
there were a few choices for hosting. You can run that again to create a new project, then copy over your app/
folder to the new project that's pre-configured for your target server.
cd ..
# create a new project, and pick a pre-configured host
npx create-remix@latest
cd my-new-remix-app
# remove the new project's app (not the old one!)
rm -rf app
# copy your app over
cp -R ../my-old-remix-app/app app