1 min read

Remix on Vercel

Learn how to use Vercel's features with Remix.
Table of Contents

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.

With Vercel, you can deploy server-rendered Remix and Remix V2 applications to Vercel with zero configuration. When using the Remix Vite plugin, static site generation using SPA mode is also supported.

It is highly recommended that your application uses the Remix Vite plugin, in conjunction with the Vercel Preset, when deploying to Vercel.

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:

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 terminal and install @vercel/remix with your preferred package manager:

pnpm
yarn
npm
pnpm i @vercel/remix

When using the Remix Vite plugin (highly recommended), you should configure the Vercel Preset to enable the full feature set that Vercel offers.

To configure the Preset, add the following lines to your vite.config file:

/vite.config.ts
import { vitePlugin as remix } from '@remix-run/dev';
import { installGlobals } from '@remix-run/node';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { vercelPreset } from '@vercel/remix/vite';
 
installGlobals();
 
export default defineConfig({
  plugins: [
    remix({
      presets: [vercelPreset()],
    }),
    tsconfigPaths(),
  ],
});

Using this Preset enables Vercel-specific functionality such as rendering your Remix application using either Node.js or Edge runtimes.

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:

/app/routes/_index.tsx
export default function IndexRoute() {
  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <h1>This route is rendered on the server</h1>
    </div>
  );
}

With Vercel, there are two ways to deploy your server rendered routes:

The runtime which is used to render your route can be configured on a per-route basis. See below for examples of how to configure your routes.

See our Function comparison table to understand whether Edge or Serverless is best for your use-case.

Serverless Functions execute using Node.js. They 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 are 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)":

/app/routes/serverless-example.tsx
export default function Serverless() {
  return <h1>Welcome to Remix with Vercel (Serverless)</h1>;
}

To configure Serverless Functions on a per-route basis, export a config object in the route.

For example, to configure the maximum duration of your function, you can use the following configuration:

/app/routes/serverless-config.tsx
// This function can run for a maximum of 5 seconds
export const config = {
  maxDuration: 5,
};
 
export default function Serverless() {
  return <h1>Welcome to Remix with Vercel (Serverless)</h1>;
}

Configuration can also be specified in a layout route, and all routes that use that layout will inherit the parent configuration.

For example, to configure all routes to use a common configuration, export the config object in the app/root layout:

/app/root.tsx
import { Outlet } from '@remix-run/react';
 
// All routes will inherit this configuration,
// unless a route overrides the config option
export const config = {
  memory: 1024,
};
 
export default function App() {
  return <Outlet />;
}

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

Edge Functions are a fast and reliable solution for delivering dynamic content to users. By default, Edge Functions deploy 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'. For example:

/app/routes/edge-example.tsx
// This route will be rendered using the Edge runtime
export const config = { runtime: 'edge' };
 
export default function Edge() {
  return <h1>Welcome to Remix@Edge</h1>;
}

Routes inherit the configuration defined in their parent layout route. Therefore, if you would like to for all routes, or a group of routes, to be rendered using the Edge runtime, you can define the configuration in a parent layout route.

/app/root.tsx
import { Outlet } from '@remix-run/react';
 
// All routes will be rendered using Edge runtime
export const config = { runtime: 'edge' };
 
export default function App() {
  return <Outlet />;
}

When a layout is configured to render using Edge, you can configure a route to use Node.js by exporting a config object with runtime: 'nodejs'.

The following example demonstrates a parent admin layout deployed on the Edge Runtime that renders a list of blog posts for an admin user.

/app/routes/posts/admin.tsx
import { Link, Outlet, useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@vercel/remix';
import { json } from '@vercel/remix';
import { getPostListings } from '~/models/post.server';
import { requireAdminUser } from '~/session.server';
 
export const config = { runtime: 'edge' };
 
export async function loader({ request }: LoaderFunctionArgs) {
  await requireAdminUser(request);
  return json({ posts: await getPostListings() });
}
 
export default function AdminPostList() {
  const { posts } = useLoaderData<typeof loader>();
  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>
  );
}

Then, create a nested $slug file under /app/routes/posts/admin/. This route will deploy as a Vercel Serverless Function:

/app/routes/posts/admin/$slug.tsx
import { json } from '@vercel/remix';
import type { LoaderFunctionArgs } from '@vercel/remix';
import { getPost } from '~/models/post.server';
import { requireAdminUser } from '~/session.server';
 
export const config = { runtime: 'nodejs' };
 
export async function loader({ request, params }: LoaderFunctionArgs) {
  // 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({ post });
}
 
export default function Post() {
  const { post } = useLoaderData<typeof loader>();
  return (
    <div>
      <p>{post.title}</p>
      <p>{post.description}</p>
    </div>
  );
}

If the parent layout route is not explicitly defined as an Edge Function, that route and its nested routes will deploy as Serverless Functions. In such cases, exporting a config object with runtime: 'nodejs' is not necessary.

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

Streaming HTTP responses with Remix on Vercel is supported, in both Edge and Serverless routes. See the Streaming page in the Remix docs for general instructions.

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:

/app/routes/defer-route.tsx
import { Suspense } from 'react';
import { Await, useLoaderData } from '@remix-run/react';
import { defer } from '@vercel/remix';
 
function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
 
export async function loader({ request }) {
  const version = process.versions.node;
 
  return defer({
    // Don't let the promise resolve for 1 second
    version: sleep(1000).then(() => version),
  });
}
 
export default function DeferredRoute() {
  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() and Await

Learn more about Streaming

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 function, 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
/app/routes/example.tsx
import type { HeadersFunction } from '@vercel/remix';
 
export const headers: HeadersFunction = () => ({
  'Cache-Control': 's-maxage=1, stale-while-revalidate=59',
});
 
export async function loader() {
  // Fetch data necessary to render content
}

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

Learn more about Caching

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
yarn
npm
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:

app/root.tsx
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

Learn more about Analytics

By default, Vercel supplies an implementation of the entry.server file which is configured for streaming to work on both Edge and Serverless runtimes. This version will be used when no entry.server file is found in the project, or when the existing entry.server file has not been modified from the base Remix template.

However, if your application requires a customized app/entry.server.jsx or app/entry.server.tsx file (for example, to wrap the <RemixServer> component with a React context), you should base it off of this template:

/app/entry.server.tsx
import { RemixServer } from '@remix-run/react';
import { handleRequest, type EntryContext } from '@vercel/remix';
 
export default async function (
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
) {
  let remixServer = <RemixServer context={remixContext} url={request.url} />;
  return handleRequest(
    request,
    responseStatusCode,
    responseHeaders,
    remixServer,
  );
}

Defining a custom server file is not supported when using the Remix Vite plugin on Vercel.

It's 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.

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:

server.ts
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:

Last updated on March 29, 2024