Serving files as routes with Remix

Last updated January 14, 2022 by Jacob Paris

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 / 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}} />