Remix on Vercel
Learn how to use Vercel's features with Remix.Remix is a fullstack, server-rendered React framework. Its built-in features for nested pages, error boundaries, transitions between loading states, and more, enable developers to create modern web apps.
You can deploy server-rendered Remix applications to Vercel with zero configuration. Remix currently does not support static site generation.
There are multiple ways to get started with Remix on Vercel:
- If you already have a project with Remix, install Vercel CLI and run the
vercel
command from your project's root directory - Clone one of our Remix example repos to your favorite git provider and deploy it on Vercel with the button below:
- Or, choose a template from Vercel's marketplace:
Vercel deployments can integrate with your git provider to generate preview URLs for each pull request you make to your Remix project.
The @vercel/remix
package exposes useful types and utilities for Remix apps deployed on Vercel, such as:
To best experience Vercel features such as streaming, Edge Functions, Serverless Functions, and more, we recommend importing utilities from @vercel/remix
rather than from standard Remix packages such as @remix-run/node
.
@vercel/remix
should be used anywhere in your code that you normally would import utility functions from the following packages:
To get started, navigate to the root directory of your Remix project with your favorite terminal and install @vercel/remix
with the following command:
pnpm i @vercel/remix
Server-Side Rendering (SSR) allows you to render pages dynamically on the server. This is useful for pages where the rendered data needs to be unique on every request. For example, checking authentication or looking at the location of an incoming request.
Remix routes defined in /app/routes
are deployed with server-side rendering by default.
The following example demonstrates a basic route that renders with SSR:
export default function SSR() {
return (
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
<h1>This route runs on SSR</h1>
</div>
);
}
There are two ways to deploy your SSR routes:
- Serverless Functions (default)
- Edge Functions
To summarize, Server-Side Rendering (SSR) with Remix on Vercel:
- Scales to zero when not in use
- Scales automatically with traffic increases
- Has framework-aware infrastructure that enables switching rendering between Edge/Node.js (Serverless) runtimes
Serverless Functions enable developers to write functions that use resources that scale up and down based on traffic demands. This prevents them from failing during peak hours, but keeps them from running up high costs during periods of low activity.
Remix API routes in /app/routes
will be deployed as Serverless Functions by default. To learn how to deploy them as Edge Functions instead, see our section on Edge Functions.
The following example demonstrates a basic route that renders a page with the heading, "Welcome to Remix with Vercel (Serverless)":
export default function Serverless() {
return (
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
<h1>Welcome to Remix with Vercel (Serverless)</h1>
</div>
);
}
You can test Serverless Functions locally with remix dev
.
See our Function comparison table to understand whether Edge or Serverless is best for your use-case.
To summarize, Serverless Functions with Remix on Vercel:
- Scale to zero when not in use
- Scale automatically as traffic increases
- Support standard Web APIs, such as
URLPattern
,Response
, and more
Learn more about Serverless Functions
Edge Functions are a fast and reliable solution for delivering dynamic content to users. By default, Edge Functions are deployed globally, and will be invoked in one of Vercel's Edge regions near your site's visitors.
Edge Functions are ideal when you need to interact with data over the network as fast as possible, such as executing OAuth callbacks, responding to webhook requests, or interacting with an API that fails if a request is not completed within a short time limit.
Remix routes defined in /app/routes
are deployed as Serverless Functions by default. To deploy a route with Edge Functions, you must export a config
object with runtime: 'edge'
, as demonstrated below:
export const config = { runtime: 'edge' };
export default function Edge() {
return (
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
<h1>Welcome to Remix@Edge</h1>
</div>
);
}
A route will inherit the runtime defined in its parent layout route by default. To opt a route out of Edge Functions, you can export a config object with runtime: 'nodejs'
.
The following example demonstrates a parent admin
route deployed on the Edge Runtime that renders a list of blog posts for an admin user.
import { Link, Outlet, useLoaderData } from '@remix-run/react';
import type { LoaderFunction } from '@vercel/remix';
import { json } from '@vercel/remix';
import { getPostListings } from '~/models/post.server';
import { requireAdminUser } from '~/session.server';
export const config = { runtime: 'edge' };
type LoaderData = {
posts: Awaited<ReturnType<typeof getPostListings>>;
};
export const loader: LoaderFunction = async ({ request }) => {
await requireAdminUser(request);
return json<LoaderData>({ posts: await getPostListings() });
};
export default function AdminPostList() {
const { posts } = useLoaderData() as LoaderData;
return (
<div>
<h1>Blog Admin</h1>
<nav>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link to={post.slug}>{post.title}</Link>
</li>
))}
</ul>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
You could then create a nested $slug
file under /app/routes/posts/admin/
, which could be deployed with our Serverless NodeJS runtime, as demonstrated below:
import { json } from '@vercel/remix';
import type { LoaderFunction } from '@vercel/remix';
import type { Post } from '~/models/post.server';
import { requireAdminUser } from '~/session.server';
export const config = { runtime: 'nodejs' };
type LoaderData = { post?: Post };
export const loader: LoaderFunction = async ({ request, params }) => {
// Authenticate the user
await requireAdminUser(request);
// Fetch the post
const post = await getPost(params.slug);
// Add it to the loader data, which
// the component can use
return json<LoaderData>({ post });
};
export default function Post() {
const { post } = useLoaderData() as LoaderData;
return (
<div>
<p>{post.title}</p>
<p>{post.description}</p>
</div>
);
}
If the parent layout route is not explicitly deployed as an Edge Function, it and its nested routes will be deployed as Serverless Functions by default. In such cases, exporting a config
object with runtime: 'nodejs'
is not necessary.
See our Function comparison table to understand whether Edge or Serverless is best for your use-case.
To summarize, Edge Functions with Remix on Vercel:
- Offer cost savings by using fewer resources than Serverless Functions
- Can execute in the region nearest to your users or nearest to data sources they depend on, based on your configuration
- Have access to the geolocation and IP address of visitors, enabling location-based personalization
Learn more about Edge Functions
You can stream HTTP responses with Remix on Vercel, in both Edge and Serverless routes.
To get started, you must first install @vercel/remix
.
Then, import handleRequest
from the @vercel/remix
package, and return it in the default function exported from your entry.server
module:
import { handleRequest } from '@vercel/remix';
import { RemixServer } from '@remix-run/react';
import type { EntryContext } from '@vercel/remix';
export default function (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const remixServer = <RemixServer context={remixContext} url={request.url} />;
return handleRequest(
request,
responseStatusCode,
responseHeaders,
remixServer,
);
}
See our section on Edge Functions to learn how to deploy a route as an Edge Function.
The amount of data you can stream in an Edge Function is limited by its memory allocation, and execution initial response time. Exceeding these limits will cause your function to fail.
You can also render loading states with Remix on Vercel using Remix's defer
method and Await
component.
When a route's loader
function returns a call to defer
, the data can be accessed in components with the useLoaderData
hook.
The following example demonstrates a route that simulates a throttled network by delaying a promise's result, and renders a loading state until the promise is resolved:
import { Suspense } from 'react';
import { Await, useLoaderData } from '@remix-run/react';
import { defer } from '@vercel/remix';
export async function loader({ request }) {
const version = process.versions.node;
return defer({
version: sleep(version, 1000),
});
}
// Don't let the promise resolve for 1 second
function sleep(val: string, ms: number) {
return new Promise((resolve) => setTimeout(() => resolve(val), ms));
}
export default function App() {
const { version } = useLoaderData();
return (
<Suspense fallback={'Loading…'}>
<Await resolve={version}>{(version) => <strong>{version}</strong>}</Await>
</Suspense>
);
}
To summarize, Streaming with Remix on Vercel:
- Offers faster Function response times, improving your app's user experience
- Allows you to return large amounts of data without exceeding Edge and Serverless Function response size limits
- Allows you to display Instant Loading UI from the server with Remix's
defer()
andAwait
Vercel's Edge Network caches your content at the edge in order to serve data to your users as fast as possible. Static caching works with zero configuration.
By adding a Cache-Control
header to responses returned by your Remix routes, you can specify a set of caching rules for both client (browser) requests and server responses. A cache must obey the requirements defined in the Cache-Control header.
Remix supports header modifications with the headers
method, which you can export in your routes defined in /app/routes
.
The following example demonstrates a route that adds Cache Control
headers which instruct the route to:
- Return cached content for requests repeated within 1 second without revalidating the content
- For requests repeated after 1 second, but before 60 seconds have passed, return the cached content and mark it as stale. The stale content will be revalidated in the background with a fresh value from your
loader
function
export async function loader() {
// Fetch data necessary to render content
}
export function headers() {
return {
'Cache-Control': 's-maxage=1, stale-while-revalidate=59',
};
}
See the full list of valid values for Cache-Control
headers to learn more.
You can also export the headers
method in your entry.server
file to apply global headers. The following example sets a global Cache-Control
header, which returns cached content for requests repeated within 60 seconds:
import { renderToString } from 'react-dom/server';
import { RemixServer } from '@remix-run/react';
import type { EntryContext } from '@vercel/remix';
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const markup = renderToString(
<RemixServer context={remixContext} url={request.url} />,
);
responseHeaders.set('Cache-Control', 's-maxage=60');
return new Response('<!DOCTYPE html>' + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
See our docs on cache limits to learn the max size and lifetime of caches stored on Vercel.
To summarize, using Cache-Control
headers with Remix on Vercel:
- Allow you to cache responses for server-rendered Remix apps using Edge or Serverless Functions
- Allow you to serve content from the cache while updating the cache in the background with
stale-while-revalidate
- Work out of the box with zero configuration
Vercel's Analytics features enable you to visualize and monitor your application's performance over time. The Analytics tab in your project's dashboard offers detailed insights into your website's visitors, with metrics like top pages, top referrers, and user demographics.
To use Analytics, navigate to the Analytics tab of your project dashboard on Vercel and select Enable in the modal that appears.
To track visitors and page views, we recommend first installing our @vercel/analytics
package by running the terminal command below in the root directory of your Remix project:
pnpm i @vercel/analytics
Then, follow the instructions below to add the Analytics
component to your app. The Analytics
component is a wrapper around Vercel's tracking script, offering a seamless integration with Remix.
Add the following component to your root
file:
import { Analytics } from '@vercel/analytics/react';
export default function App() {
return (
<html lang="en">
<body>
<Analytics />
</body>
</html>
);
}
To summarize, Analytics with Remix on Vercel:
- Enables you to track traffic and see your top-performing pages
- Offers you detailed breakdowns of visitor demographics, including their OS, browser, geolocation and more
It is usually not necessary to define a custom server.js file within your Remix application when deploying to Vercel. In general, we do not recommend it.
However, if your project requires a custom server
file, you will need to install @vercel/remix
and import createRequestHandler
from @vercel/remix/server
. The following example demonstrates a basic server.js
file:
import { createRequestHandler } from '@vercel/remix/server';
import * as build from '@remix-run/dev/server-build';
export default createRequestHandler({
build,
mode: process.env.NODE_ENV,
getLoadContext() {
return {
nodeLoadContext: true,
};
},
});
See our Frameworks documentation page to learn about the benefits available to all frameworks when you deploy on Vercel.
Learn more about deploying Remix projects on Vercel with the following resources: