Skip to content

Full stack foundations

Published: at 03:23 PM

Table of contents

Open Table of contents

links is a special export in Remix (checkout styling guide in the Remix docs)

This links export allow us to define an array of objects which would then be constructed into link tags on the page.

These links can be configured to be present based on whether a route is active or not.

Responsive favicon

Solution #1: Without Remix

Add the following link tag in the head section.

<link rel="icon" type="image/svg+xml" href="/favicon.svg" />

Solution #2: With Remix

Export the special links object in the component.

export const links: LinksFunction = () => {
	return [
		{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
	]
}

This approach lets us add links from any component within the nested routes.

Asset import caching issue

Let’s say we want to update the color of the favicon and we update the svg file. We will notice that we still get the old icon and that is because of the default cache time of 1 hour.

To fix this, let’s add a query param to the icon path whenever we update it. It can be anything and in this example we add ?v=2 (version 2?) to the path.

export const links: LinksFunction = () => {
	return [
		{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg?v=2' },
	]
}

The problem with this approach is that we need to manually update the query param whenever we modify the icon file. We can avoid this with the help of Remix. Let’s import the file and add it to the config rather than hardcoding tha path.

import faviconAssetUrl from './assets/favicon.svg'

export const links: LinksFunction = () => {
	return [
		{ rel: 'icon', type: 'image/svg+xml', href: faviconAssetUrl },
	]
}

With this approach, a fingerprint gets added to the file name based on the contents of the file.

Example:

favicon-32HPPLKM.svg

favicon-${fingerprint}.svg

Adding global styles to a Remix app

We follow the same approach that we took for the favicon, except that the value of rel would be stylesheet

import faviconAssetUrl from './assets/favicon.svg'
import fontStylesheetUrl from './styles/font.css'

export const links: LinksFunction = () => {
	return [
		{ rel: 'icon', type: 'image/svg+xml', href: faviconAssetUrl },
        { rel: 'stylesheet', href: fontStylesheetUrl },
	]
}

Using Postcss and Tailwind CSS in Remix

Step 1:

Add PostCSS config. Create a file postcss.config.js in the root of remix app with the following contents.

export default {
	plugins: {
		'tailwindcss/nesting': {},
		tailwindcss: {},
		autoprefixer: {},
	},
}

Step 2:

Add Tailwind config. Create a file tailwind.config.ts in the root of remix app with the following contents.

import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme.js'

export default {
	content: ['./app/**/*.{ts,tsx,jsx,js}'],
	theme: {
		extend: {
			fontFamily: {
				sans: [
					'Nunito Sans',
					'Nunito Sans Fallback',
					...defaultTheme.fontFamily.sans,
				],
			},
		},
	},
} satisfies Config

Step 3:

Create a tailwind.css file at app/styles/tailwind.css with the following contents.

@tailwind base;
@tailwind components;
@tailwind utilities;

Step 4:

Add the tailwind css file to the links export.

import faviconAssetUrl from './assets/favicon.svg'
import fontStylesheetUrl from './styles/font.css'
import tailwindStylesheetUrl from './styles/tailwind.css'

export const links: LinksFunction = () => {
	return [
		{ rel: 'icon', type: 'image/svg+xml', href: faviconAssetUrl },
        { rel: 'stylesheet', href: fontStylesheetUrl },
        { rel: 'stylesheet', href: tailwindStylesheetUrl },
	]
}

That’s all! We should be able to start using Tailwind in our remix app.

Bundling CSS in Remix

What if we need to add some styles that are provided by some third party libraries we are using. What if there is a way to add a global.css file to our app and that will contain all of our common and library specific css?

Remix allows you to import CSS files (side-effect imports) that are bundled automatically.

import stylesheetUrl from './styles1.css' // <-- you use the URL in the links export
import './global.css' // <-- this will be bundled

So, if you just import the CSS file without an “import clause” (the stylesheetUrl variable in the example above), it will be bundled for you.

However, we are still responsible for everything on the page between the and the tags, so if we want the bundled CSS file on the page then we need to make sure we add it to the links. Remix gives us access to that URL through a special package called @remix-run/css-bundle:

import stylesheetUrl from './styles1.css' // <-- you use the URL in the links export
import './global.css' // <-- this will be bundled
import { cssBundleHref } from '@remix-run/css-bundle'

export const links: LinksFunction = () => {
	return [
		...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : []),
		{ rel: 'icon', type: 'image/svg+xml', href: faviconAssetUrl },
		{ rel: 'stylesheet', href: fontStylesheetUrl },
		{ rel: 'stylesheet', href: tailwindStylesheetUrl },
	]
}

Why can’t we just import all of the styles as side-effect imports? Even the tailwind.css and font.css files?

Reason 1: Any change in any of the files would update the fingerprint of the single css-bundle... file which would mean that we do not make use of the cache.

Reason 2: With the bundling approach, we get control over the order of the CSS files which in turn affects the priority of the styles.

Further reading

link tag documentation

Using Tailwind with preprocessors

CSS bundling in Remix

Checkout modulepreload