Trying Out Remix
trying-outI came across Supporting Remix with full stack Cloudflare Pages on Cloudflare blog (yes I’m a CF fan) and Remix piqued my interest. Mostly because it addresses my concerns with “modern” web development: the wiring boilerplate is mostly gone, there is no 100 file setup to get a workable dev environment. And of course because it’s outside my comfort zone - so maybe I’ll learn something.
Getting started
There’s a code example right on the home page but I’m having hard time building a mental model from just that. Luckily there is also a bit Get started button which takes me to a tutorial. Great, let’s follow that.
Few more commands
|
|
And we’re up.
Then there is text that really speaks to me
If you want, take a minute and poke around the starter template, there’s a lot of information in there.
Oh yes I intend to do that π
Let’s make a git repo to keep track of changes. Conveniently there is aready a
gitignore
.
|
|
Let’s now see what we have
|
|
Honestly less than I expected. And I mean this in the best possible way. I like
environments which I can understand to the point of being able to write manually
from scratch. Yest I’m looking at you create-react-app
and other humongous
frontend toolchains. I feel at home with Go where you have your dependencies
file and a single go
command.
Back to source files; there’s a typescript config but no webpack config (yay?) or similar. This is in the typescript config
|
|
Great! Let’s find the entrypoint now to better understand this.
Since I’ve been instructed to use npm run dev
this means that package.json
will define the action.
|
|
And it does, no magic here.
There is something the looks like remix configuration (remix.config.js
)
|
|
And apparently it points to app
directory. Conveniently there I can find
entry.client.tsx
and entry.server.tsx
which I guess are my entrypoints. What
I found slightly strange is .tsx
(which I believe is JSX for typescript) for
the server.
Maybe time to read some more docs π€·
Baby’s first code
Confused for a moment…apparently docs are not up to date, the links are now in
app/routes/index.tsx
. Oh well, I’ll manage. I added the li
as instructed
|
|
and then of course it fails to compile as Link
is not defined. Guessed the
import based on imports in root.tsx
to be
|
|
It works!
Then created app/routes/posts/index.tsx
as
|
|
And non-surprisingly it shows up now. Time for some remix magic now.
Loaders
If your web dev background is primarily in the last few years, you’re probably used to creating two things here: an API route to provide data and a frontend component that consumes it. In Remix your frontend component is also its own API route and it already knows how to talk to itself on the server from the browser. That is, you don’t have to fetch it.
|
|
And it show up as expected.
Then the tutorial moves on to refactoring, extracting data and fetching from an external data source but I’m more interested in this mechanics as it seems to me this is the meat and the potatoes of remix. So what is going on?
Well apparently the useLoaderData
hook is automatically tied to corresponding
exported loader
in the same route. By convention instead of us manually wiring
up routing, neat. But I have questions now π Can I pass parameters? Is
there useQuery
-like magical caching and deduplication? Authentication? CORS?
More interestingly: if I looks at network in devtools I don’t see an XHR
request, meaning our data is pre-rendered on the server. Super neat! But if i
navigate from index to posts I see a request to
http://localhost:3000/posts?_data=routes%2Fposts%2Findex
which directly
returns my data.
Routes
Reading on
the tutorial actually explains how to parametrize aka how to do dynamic route params
.
It’s again done by convention, not explicit config: file name is a placeholder for a parameter. Clever?
I’ll admit my knee jerk reaction to this is “ugh, ugly. I want my router”. But it may work? I don’t know, would actually need to do a larger project to evaluate properly. In any case there is a way to get an overview of the routes:
|
|
So I can still have my cake (easily overview and discover routes) and eat it (not write the router) too.
Ok, so how does this paramterized component look like?
|
|
And sure enough I can now click on one of the posts and it renders
So what is happening here?
- My url path
/posts/my-first-post
is matched against theposts/:slug
route and my component is rendered - then the
useLoaderData
hook fires and data for self url is requested - more concretely when navigating on frontend a request toposts/my-first-post?_data=routes%2Fposts%2F%24slug
is dispatched (I’m assuming this is bypassed when server-side rendered and loader is called directly) - server side matches this against routes again, picks up my page but since
we’re requesting data it now calls the
loader
with query path parameters passed in as named values inparams
. The basic example is just returning a string here but it could be any json, probably with data from some external data source as well - response from the loader is wired (via fetch/XHR or server side rendering) to
the hook and the string returned from the loader is bound to
slug
constant.
Now the analogy from the tutorial that loader is the controller and we’re using react as a view layer makes sense π
Then I followed the instructions to get markdown rendering up an running which is well covered in the tutorial and standard javascript so I’ll skip the details.
An experiment
But meanwhile I started wondering…can I put multiple components on screen and each will automagically fetch it’s data? I could peruse the docs some more…or I can just try it out.
I created this app/routes/footer.tsx
|
|
Yes, very silly, calling a server to get current year, but I just want to test things out π
One remark while I figure things out: tooling is quite responsive and helpful: change detection, auto-recompilation and reloading out of the box.
|
|
Then in $slug.tsg
|
|
And a cryptic error appears
What is slug doing here? Sprinkling in some logging I notice that year
actually holds data from $slug.txs
loader, not my footer loader. So I’m
holding it wrong - back to the docs it is.
Styles
Next interesting bit is that components and also include styles
|
|
and this gets picked up by Links
component in index.tsx
.
Index routes
But the real fun begins with index routes
. By adding this to
app/routes/admin.tsx
|
|
things can now render inside this component. E.g. visiting /admin/new
will
render first app/routes/admin.tsx
but inside the Outlet
there will be
/app/routes/admin/new.tsx
. This time with working data fetching π But
does this mean I can only structure my data-fetching components hierarchically?
Reading on I’m slightly surprised to see that remix includes its own forms. How
do they differ from plain old html <form>
? I’m guessing some automagical
wiring to the backend, maybe shared validation logic π€
And sure enough, there is wiring by convention
|
|
So what happens when I submit this form? Feels very smooth, let’s look under the cover.
- a
POST
request is made to/admin/new?_data=routes%2Fadmin%2Fnew
with regular form data payload. - but the response is interesting: status is set to
204 No Content
and there is an interesting header:X-Remix-Redirect: /admin
- apparently the “redirect” is then actually just frontend navigation to new
route as there are no more requests except one for new data
/admin?_data=routes%2Fadmin
Other stuff
Then there is another much longer tutorial (also available as a video) that dives deeper and covers more topics (like cookies, authentication, validation, error handling, databases…).
How about deployment? I did start this with the intent of deploying to Cloudflare but the post is already getting long and the app as-is is not really suitable (direct filesystem access is not supported) for deploying, so maybe in another post.
Conclusion
We’ll as much as I’m not a home at writing frontend code and I dislike the idea of “full stack javascript” this actually did not hurt a bit and I can see how time can be really useful for doing a very dynamic client app that as some server side components.
Now, I would probably not pick this stack for a new project, mostly to me not being comfortable with Node, but I might just use it for an opinionated React toolchain with SSR and all other goodies.
Last modified on 2021-12-19
Previous Running amd64 docker images with Podman on Apple Silicon (M1)Next Speed is a feature; thoughts on modern web performance