diff --git a/content/articles/articles.md b/content/articles/articles.md index 4b529c2..9400ea9 100644 --- a/content/articles/articles.md +++ b/content/articles/articles.md @@ -1,5 +1,485 @@ --- items: + - type: articles + body: >- + [Remix](https://remix.run) is a full-stack web framework that lets you focus on the user interface and work back through web fundamentals to deliver a fast, slick, and resilient user experience. This is what excited me when I first read about Remix. I'm a big advocate for progressively enhancing web applications and this framework seems to take this approach by simply using web fundamentals. + + + This is not a step-by-step guide on how to make a web application with Remix, but rather some features I noticed and wanted to highlight. That might come later. If you are looking for that, [Remix.run](http://Remix.run) is an amazing landing page and contains very well written documentation that will take you through all the reasons why they build this framework, what it offers and how it works. Seriously, one of the best landing pages I've seen in the past few years. Please, have a read there and come back for my view as a [Next.js](https://nextjs.org/) fanboy. + + ## Routing mechanism + + Like Next.js, Remix uses a file-based routing system. In `/app` (where your app will live) you can create a folder called `routes`. Every file you add (e.g. `/app/routes/example` will create a route and page under `http://website.com/example`. If you're unfamiliar with this concept, please read my article [A simple, yet detailed introduction to Next.js](https://www.davebitter.com/articles/a-simple-yet-detailled-introduction-to-next-js). Next.js and Remix work nearly the same with some exceptions like creating a dynamic page (`[slug]` vs. `$slug`. If this is not the way you want to work, you can also define your routes in an object for both of these frameworks. + + ### Nested routes + + Remix seems to heavily rely on the usage of nested routes as "partials". Let's say you are building an admin view. You might have multiple routes for several admin pages: + + ``` + + - app + - routes + - admin.tsx + - admin + - new.tsx + - edit.tsx + $user.tsx + + ``` + + All these admin pages might share a special navigation bar for logged in admins. Naturally, you could load a reusable component in the markup of all these pages, but that sounds more like a partial that you used to use back when building websites with templating languages like Pug or Handlebars. Remix allows you to just load the partial in `app/routes/admin.tsx` and add a Remix component called `` . This component serves as a placeholder where a child route can be rendered. In this case, that would be the new and edit route. I really like this approach of templating out pages. + + ### Scoped JS and CSS per nested route + + These nested routes are not just useful as partials, but will allow Remix to also easily chunck-up your nested routes for JS and CSS bundles. Naturally, it knows what pieces to load as you load them per nested route. This makes these nested routes true small little routes and optimises the loading of resources. + + + This concept is fundamental to how Remix works. You can read more about nested routes [here](https://remix.run/docs/en/v1/guides/routing) and CSS scoping with `` [here](https://remix.run/docs/en/v1/guides/styling). + + ## Server- and client-side code + + We've seen great strides by Next.js to make it easy for developers to expose server-side functions right next to client-side code. You can read more about that in my article [Next.js page generation](https://www.davebitter.com/articles/next-js-page-generation). At a first glance, Remix has an evenly convenient, if not better solution. I feel like Remix takes it to the next level by fully focussing on not having to use client-side JS as a standard, but rather an afterthought. + + ### Loaders + + Many web applications need to fetch data. You can use `loader` functions in combination with a `useLoaderData` hook to set this up. + + ```jsx + + import type { LoaderFunction } from "remix"; + + import type { User } from "@prisma/client"; + + import { db } from "~/utils/db.server"; + + + type LoaderData = { users: Array }; + + export let loader: LoaderFunction = async () => { + const data: LoaderData = { + users: await db.user.findMany() + }; + + return { data }; + }; + + + export default function Users() { + const data = useLoaderData(); + + return ( +
    + {data.map(user => ( +
  • {user.name}
  • + ))} +
+ ); + } + + ``` + + There is quite some Typescript-specific code here, but in its essence there is an async function exported called `loader`. This function fetches some data and returns an object with data. Note that you can return multiple pieces of data from various sources here. You can now access all this data using the `useLoaderData` hook. An important aspect of this is that no client-side JS is needed here. You just want some data from your server-side function and then use it to render a list on the server to serve to the client. + + ### Actions + + So what about handling things like forms? I need to handle that on the client right? Well, not necessarily. Forms can easily be handled with a `method` attribute on the form element. Sure, you might want to make a fancy multi-step form, but this is where progressive enhancement pops up again. Just make a form with a `method` and let it post using the web standards. You then export an `action` function that will handle the posting of the form. Take a look at this code snippet of the [Jokes app tutorial](https://remix.run/docs/en/v1/tutorials/jokes) I followed in preparation for this article. You can find my full end result in [this repository](https://github.com/DaveBitter/remix-jokes). + + ```jsx + + import type { ActionFunction } from "remix"; + + import { redirect } from "remix"; + + import { db } from "~/utils/db.server"; + + + export const action: ActionFunction = async ({ + request + }) => { + const form = await request.formData(); + const name = form.get("name"); + const content = form.get("content"); + // we do this type check to be extra sure and to make TypeScript happy + // we'll explore validation next! + if ( + typeof name !== "string" || + typeof content !== "string" + ) { + throw new Error(`Form not submitted correctly.`); + } + + + const fields = { name, content }; + + + const joke = await db.joke.create({ data: fields }); + return redirect(`/jokes/${joke.id}`); + }; + + + export default function NewJokeRoute() { + return ( +
+

Add your own hilarious joke

+
+
+ +
+
+