Jacob Paris
← Back to all content

Serving files as routes with Remix

I needed to host some legal documents on a website, but I also wanted to be able to provide a plain text source so that the legal team could make their own modifications.

Then, if they have any changes, all I have to do is upload the new file instead of changing specific lines piece by piece.

The result of this is a /terms-of-service route that renders a well formatted article and a /terms-of-service.md route that shows the raw markdown source.

Create a layout route __markdown.tsx

import {Link, Outlet, useLocation} from 'remix'
export default function MarkdownLayout() {
const location = useLocation()
return (
<article className="mx-auto max-w-prose">
<div className="pb-12 mx-auto my-6 prose text-gray-500 prose-sky">
<Outlet />
View printable

Create a dynamic route that serves the file __markdown/$file.tsx

import ReactDOMServer from 'react-dom/server'
import {
} from 'remix'
const allowedFiles = [
export const loader: LoaderFunction = async ({
}) => {
if (!allowedFiles.includes(params.file)) {
return redirect('/')
const files = {
'terms-of-sale': import(
'terms-of-use': import(
'privacy-policy': import(
'financial-disclosure': import(
const file = await files[params.file].then(
(mod) => mod.default,
return ReactDOMServer.renderToString(file())
export default function PostSlug() {
const __html = useLoaderData()
return (
<div dangerouslySetInnerHTML={{__html}} />
Professional headshot

Hi, I'm Jacob

Hey there! I'm a developer, designer, and digital nomad with a background in lean manufacturing.

About once per month, I send an email with new guides, new blog posts, and sneak peeks of what's coming next.

Everyone who subscribes gets access to the source code for this website and every example project for all my tutorials.

Stay up to date with everything I'm working on by entering your email below.

Unsubscribe at any time.