Dynamic Links for Remix.Run
Tags: remix, react, webdev, typescript
Published: 2022-07-06 13:33:36

I want to open this with a disclaimer. I have no idea if this is best practice or even something we should be allowed to do **but** it worked for me and my use case and thought it was super cool.

Ok, now to the good part. In Remix, which I absolutely love so far, there are a few functions you can export to accomplish many things. `LoaderFunction` to do server side fetching prior to page load. `ActionFunction` to handle post request, mostly used for form submissions. `MetaFunction` to handle meta data and lastly a `LinkFunction` to add stylesheets, script tags, preload, etc. Read more about those [here](https://remix.run/docs/en/v1/api/conventions#route-module-api).

The problem _I think_ exist is that the first three I mentioned, have access to the Remix context. So in these functions you have access to the node request, any params in the URL, etc. In the last one, the `LinkFunction` you do not. Now that _does_ make sense to me. Maybe including that context slows down the overall page flow or maybe can cause a flicker of styles, not sure. I might start a discussion on Github to learn more about why the `LinkFunction` doesn't include the Remix context.

So what happens if you need to include a stylesheet based on a database query, or a cookie value? Lucky for you, this article tells you how.

In Remix, not all routes need to be react components and/or real pages on your website. You can just export a loader or action (or both) with no default export and **tada** you have an api endpoint. So lets try this out in conjunction to my link function dilemma...

In your `root.tsx` file, you export a function called `links`, this allows you to include links globally across your entire application. In my use case, I have a theme stored in my database that is user generated and need to load those styles in as well. 

```tsx
// root.tsx
export function links() {
  return [
    { rel: "stylesheet", href: styles },
    { rel: "stylesheet", href: "/user-theme.css" },
  ];
}
```

Create a new file called `user-theme[.]css.tsx`. The bracket syntax looks funky but that how you can escape the period. I have used this syntax for robots[.]txt.tsx and sitemap[.]xml.tsx files, works great.

```tsx
//user-theme[.].css
export const loader: LoaderFunction = async ({ request }) => {
  const { data, error } = await supabase.from("themes").select("*").maybeSingle();

  /**
   * data.styles looks like this...
   * {
   *   "--primary-color": "#hexvalue",
   *   "--secondary-color": "#hexvalue",
   *   ...
   * }
   */
  let cssString = "";
  if (data && data.styles) {
    const { styles } = data;
    const cssVars = Object.keys(styles).reduce((acc, key) => {
      return [
        ...acc,
        `${key}: ${styles[key]};`
      ];
    }, []).join("\n");

    cssString = `:root{${cssVars}}`
  }

  return new Response(cssString, {
    headers: {
      "Content-type": "text/css; charset=UTF-8",
    },
  });
};
```

So it looks like a lot is happening there but its quite simple. I am querying the database for the information I need, then parsing the JSON into a css string. I am using css variables so I inject those into a `:root` declaration and ship that with the `text/css` content type.

That it! Now my dynamic css from the database query is visible on my browser, all server side. There is no client side request, no weird flicker of styles while it loads.

Let me know what you think of this approach! I know my use case was database queries, but you can also access the request cookie and return a dark mode stylesheet and really anything you want.