Skip to content

Remix

Reference doc for the `sst.aws.Remix` component.

The Remix component lets you deploy a Remix app to AWS.

Minimal example

Deploy a Remix app that’s in the project root.

sst.config.ts
new sst.aws.Remix("MyWeb");

Change the path

Deploys the Remix app in the my-remix-app/ directory.

sst.config.ts
new sst.aws.Remix("MyWeb", {
path: "my-remix-app/"
});

Add a custom domain

Set a custom domain for your Remix app.

sst.config.ts
new sst.aws.Remix("MyWeb", {
domain: "my-app.com"
});

Redirect www to apex domain

Redirect www.my-app.com to my-app.com.

sst.config.ts
new sst.aws.Remix("MyWeb", {
domain: {
name: "my-app.com",
redirects: ["www.my-app.com"]
}
});

Link resources to your Remix app. This will grant permissions to the resources and allow you to access it in your app.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
new sst.aws.Remix("MyWeb", {
link: [bucket]
});

You can use the SDK to access the linked resources in your Remix app.

app/root.tsx
import { Resource } from "sst";
console.log(Resource.MyBucket.name);

Constructor

new Remix(name, args?, opts?)

Parameters

RemixArgs

assets?

Type Input<Object>

Configure how the Remix app assets are uploaded to S3.

By default, this is set to the following. Read more about these options below.

{
assets: {
textEncoding: "utf-8",
versionedFilesCacheHeader: "public,max-age=31536000,immutable",
nonVersionedFilesCacheHeader: "public,max-age=0,s-maxage=86400,stale-while-revalidate=8640"
}
}

assets.fileOptions?

Type Input<Object[]>

Specify the Content-Type and Cache-Control headers for specific files. This allows you to override the default behavior for specific files using glob patterns.

Apply Cache-Control and Content-Type to all zip files.

{
assets: {
fileOptions: [
{
files: "**/*.zip",
contentType: "application/zip",
cacheControl: "private,no-cache,no-store,must-revalidate"
}
]
}
}

Apply Cache-Control to all CSS and JS files except for CSS files with index- prefix in the main/ directory.

{
assets: {
fileOptions: [
{
files: ["**/*.css", "**/*.js"],
ignore: "main/index-*.css",
cacheControl: "private,no-cache,no-store,must-revalidate"
}
]
}
}
assets.fileOptions[].cacheControl?

Type string

The Cache-Control header to apply to the matched files.

assets.fileOptions[].contentType?

Type string

The Content-Type header to apply to the matched files.

assets.fileOptions[].files

Type string | string[]

A glob pattern or array of glob patterns of files to apply these options to.

assets.fileOptions[].ignore?

Type string | string[]

A glob pattern or array of glob patterns of files to exclude from the ones matched by the files glob pattern.

assets.nonVersionedFilesCacheHeader?

Type Input<string>

Default “public,max-age=0,s-maxage=86400,stale-while-revalidate=8640”

The Cache-Control header used for non-versioned files, like index.html. This is used by both CloudFront and the browser cache.

The default is set to not cache on browsers, and cache for 1 day on CloudFront.

{
assets: {
nonVersionedFilesCacheHeader: "public,max-age=0,no-cache"
}
}

assets.textEncoding?

Type Input<utf-8 | iso-8859-1 | windows-1252 | ascii | none>

Default “utf-8”

Character encoding for text based assets, like HTML, CSS, JS. This is used to set the Content-Type header when these files are served out.

If set to "none", then no charset will be returned in header.

{
assets: {
textEncoding: "iso-8859-1"
}
}

assets.versionedFilesCacheHeader?

Type Input<string>

Default “public,max-age=31536000,immutable”

The Cache-Control header used for versioned files, like main-1234.css. This is used by both CloudFront and the browser cache.

The default max-age is set to 1 year.

{
assets: {
versionedFilesCacheHeader: "public,max-age=31536000,immutable"
}
}

buildCommand?

Type Input<string>

Default “npm run build”

The command used internally to build your Remix app.

If you want to use a different build command.

{
buildCommand: "yarn build"
}

buildDirectory?

Type Input<string>

Default “build”

The directory where the build output is located. This should match the value of buildDirectory in the Remix plugin section of your Vite config.

cachePolicy?

Type Input<string>

Default A new cache plolicy is created

Configure the Remix app to use an existing CloudFront cache policy.

By default, a new cache policy is created for it. This allows you to reuse an existing policy instead of creating a new one.

{
cachePolicy: "658327ea-f89d-4fab-a63d-7e88639e58f6"
}

dev?

Type false | Object

Configure how this component works in sst dev.

Instead of deploying your Remix app, this starts it in dev mode. It’s run as a separate process in the sst dev multiplexer. Read more about sst dev.

To disable dev mode, pass in false.

dev.autostart?

Type Input<boolean>

Default true

Configure if you want to automatically start this when sst dev starts. You can still start it manually later.

dev.command?

Type Input<string>

Default “npm run dev”

The command that sst dev runs to start this in dev mode.

dev.directory?

Type Input<string>

Default Uses the path

Change the directory from where the command is run.

dev.title?

Type Input<string>

The title of the tab in the multiplexer.

dev.url?

Type Input<string>

Default http://url-unavailable-in-dev.mode

The url when this is running in dev mode.

Since this component is not deployed in sst dev, there is no real URL. But if you are using this component’s url or linking to this component’s url, it can be useful to have a placeholder URL. It avoids having to handle it being undefined.

domain?

Type Input<string | Object>

Set a custom domain for your Remix app.

Automatically manages domains hosted on AWS Route 53, Cloudflare, and Vercel. For other providers, you’ll need to pass in a cert that validates domain ownership and add the DNS records.

By default this assumes the domain is hosted on Route 53.

{
domain: "example.com"
}

For domains hosted on Cloudflare.

{
domain: {
name: "example.com",
dns: sst.cloudflare.dns()
}
}

Specify a www. version of the custom domain.

{
domain: {
name: "domain.com",
redirects: ["www.domain.com"]
}
}

domain.aliases?

Type Input<string[]>

Alias domains that should be used. Unlike the redirect option, this keeps your visitors on this alias domain.

So if your users visit app2.domain.com, they will stay on app2.domain.com in their browser.

{
domain: {
name: "app1.domain.com",
aliases: ["app2.domain.com"]
}
}

domain.cert?

Type Input<string>

The ARN of an ACM (AWS Certificate Manager) certificate that proves ownership of the domain. By default, a certificate is created and validated automatically.

The certificate will be created in the us-east-1 region as required by AWS CloudFront. If you are creating your own certificate, you must also create it in us-east-1.

To manually set up a domain on an unsupported provider, you’ll need to:

  1. Validate that you own the domain by creating an ACM certificate. You can either validate it by setting a DNS record or by verifying an email sent to the domain owner.
  2. Once validated, set the certificate ARN as the cert and set dns to false.
  3. Add the DNS records in your provider to point to the CloudFront distribution URL.
{
domain: {
name: "domain.com",
dns: false,
cert: "arn:aws:acm:us-east-1:112233445566:certificate/3a958790-8878-4cdc-a396-06d95064cf63"
}
}

domain.dns?

Type Input<false | sst.aws.dns | sst.cloudflare.dns | sst.vercel.dns>

Default sst.aws.dns

The DNS provider to use for the domain. Defaults to the AWS.

Takes an adapter that can create the DNS records on the provider. This can automate validating the domain and setting up the DNS routing.

Supports Route 53, Cloudflare, and Vercel adapters. For other providers, you’ll need to set dns to false and pass in a certificate validating ownership via cert.

Specify the hosted zone ID for the Route 53 domain.

{
domain: {
name: "example.com",
dns: sst.aws.dns({
zone: "Z2FDTNDATAQYW2"
})
}
}

Use a domain hosted on Cloudflare, needs the Cloudflare provider.

{
domain: {
name: "example.com",
dns: sst.cloudflare.dns()
}
}

Use a domain hosted on Vercel, needs the Vercel provider.

{
domain: {
name: "example.com",
dns: sst.vercel.dns()
}
}

domain.name

Type Input<string>

The custom domain you want to use.

{
domain: {
name: "example.com"
}
}

Can also include subdomains based on the current stage.

{
domain: {
name: `${$app.stage}.example.com`
}
}

domain.redirects?

Type Input<string[]>

Alternate domains to be used. Visitors to the alternate domains will be redirected to the main name.

Use this to create a www. version of your domain and redirect visitors to the apex domain.

{
domain: {
name: "domain.com",
redirects: ["www.domain.com"]
}
}

environment?

Type Input<Record<string, Input<string>>>

Set environment variables in your Remix app. These are made available:

  1. In remix build, they are loaded into process.env.
  2. Locally while running through sst dev.
{
environment: {
API_URL: api.url,
STRIPE_PUBLISHABLE_KEY: "pk_test_123"
}
}

invalidation?

Type Input<false | Object>

Default {paths: “all”, wait: false}

Configure how the CloudFront cache invalidations are handled. This is run after your Remix app has been deployed.

Wait for all paths to be invalidated.

{
invalidation: {
paths: "all",
wait: true
}
}

invalidation.paths?

Type Input<string[] | all | versioned>

Default “all”

The paths to invalidate.

You can either pass in an array of glob patterns to invalidate specific files. Or you can use one of these built-in options:

  • all: All files will be invalidated when any file changes
  • versioned: Only versioned files will be invalidated when versioned files change

Invalidate the index.html and all files under the products/ route. This counts as two invalidations.

{
invalidation: {
paths: ["/index.html", "/products/*"]
}
}

invalidation.wait?

Type Input<boolean>

Default false

Configure if sst deploy should wait for the CloudFront cache invalidation to finish.

Waiting for this process to finish ensures that new content will be available after the deploy finishes. However, this process can sometimes take more than 5 mins.

{
invalidation: {
wait: true
}
}

Type Input<any[]>

Link resources to your Remix app. This will:

  1. Grant the permissions needed to access the resources.
  2. Allow you to access it in your site using the SDK.

Takes a list of resources to link to the function.

{
link: [bucket, stripeKey]
}

path?

Type Input<string>

Default ”.”

Path to the directory where your Remix app is located. This path is relative to your sst.config.ts.

By default it assumes your Remix app is in the root of your SST app.

If your Remix app is in a package in your monorepo.

{
path: "packages/web"
}

permissions?

Type Input<Object[]>

Permissions and the resources that the server function in your Remix app needs to access. These permissions are used to create the function’s IAM role.

Allow reading and writing to an S3 bucket called my-bucket.

{
permissions: [
{
actions: ["s3:GetObject", "s3:PutObject"],
resources: ["arn:aws:s3:::my-bucket/*"]
},
]
}

Perform all actions on an S3 bucket called my-bucket.

{
permissions: [
{
actions: ["s3:*"],
resources: ["arn:aws:s3:::my-bucket/*"]
},
]
}

Grant permissions to access all resources.

{
permissions: [
{
actions: ["*"],
resources: ["*"]
},
]
}

permissions[].actions

Type string[]

The IAM actions that can be performed.

{
actions: ["s3:*"]
}

permissions[].resources

Type Input<string>[]

The resourcess specified using the IAM ARN format.

{
resources: ["arn:aws:s3:::my-bucket/*"]
}

server?

Type Object

Default {architecture: “x86_64”, memory: “1024 MB”}

Configure the Lambda function used for server.

server.architecture?

Type Input<x86_64 | arm64>

Default “x86_64”

The architecture of the server function.

{
server: {
architecture: "arm64"
}
}

server.edge?

Type Input<Object>

Configure CloudFront Functions to customize the behavior of HTTP requests and responses at the edge.

server.edge.viewerRequest?

Type Input<Object>

Configure the viewer request function.

The viewer request function can be used to modify incoming requests before they reach your origin server. For example, you can redirect users, rewrite URLs, or add headers.

server.edge.viewerRequest.injection

Type Input<string>

The code to inject into the viewer request function.

By default, a viewer request function is created to:

  • Disable CloudFront default URL if custom domain is set.
  • Add the x-forwarded-host header.

The given code will be injected at the end of this function.

async function handler(event) {
// Default behavior code
// User injected code
return event.request;
}

To add a custom header to all requests.

{
server: {
edge: {
viewerRequest: {
injection: `event.request.headers["x-foo"] = "bar";`
}
}
}
}

You can use this add basic auth, check out an example.

server.edge.viewerRequest.kvStores?

Type Input<Input<string>[]>

The KV stores to associate with the viewer request function.

Takes a list of CloudFront KeyValueStore ARNs.

{
server: {
edge: {
viewerRequest: {
kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"]
}
}
}
}
server.edge.viewerResponse?

Type Input<Object>

Configure the viewer response function.

The viewer response function can be used to modify outgoing responses before they are sent to the client. For example, you can add security headers or change the response status code.

server.edge.viewerResponse.injection

Type Input<string>

The code to inject into the viewer response function.

By default, no viewer response function is set. A new function will be created with the provided code.

async function handler(event) {
// User injected code
return event.response;
}

To add a custom header to all responses.

{
server: {
edge: {
viewerResponse: {
injection: `event.response.headers["x-foo"] = {value: "bar"};`
}
}
}
}
server.edge.viewerResponse.kvStores?

Type Input<Input<string>[]>

The KV stores to associate with the viewer response function.

Takes a list of CloudFront KeyValueStore ARNs.

{
server: {
edge: {
viewerResponse: {
kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"]
}
}
}
}

server.install?

Type Input<string[]>

Dependencies that need to be excluded from the server function package.

Certain npm packages cannot be bundled using esbuild. This allows you to exclude them from the bundle. Instead they’ll be moved into a node_modules/ directory in the function package.

This will allow your functions to be able to use these dependencies when deployed. They just won’t be tree shaken. You however still need to have them in your package.json.

Esbuild will ignore them while traversing the imports in your code. So these are the package names as seen in the imports. It also works on packages that are not directly imported by your code.

{
server: {
install: ["sharp"]
}
}

server.layers?

Type Input<Input<string>[]>

A list of Lambda layer ARNs to add to the server function.

{
server: {
layers: ["arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1"]
}
}

server.memory?

Type Input<${number} MB | ${number} GB>

Default “1024 MB”

The amount of memory allocated to the server function. Takes values between 128 MB and 10240 MB in 1 MB increments.

{
server: {
memory: "2048 MB"
}
}

server.runtime?

Type Input<nodejs18.x | nodejs20.x | nodejs22.x>

Default “nodejs20.x”

The runtime environment for the server function.

{
server: {
runtime: "nodejs22.x"
}
}

transform?

Type Object

Transform how this component creates its underlying resources.

transform.assets?

Type BucketArgs | (args: BucketArgs, opts: ComponentResourceOptions, name: string) => void

Transform the Bucket resource used for uploading the assets.

transform.cdn?

Type CdnArgs | (args: CdnArgs, opts: ComponentResourceOptions, name: string) => void

Transform the CloudFront CDN resource.

transform.imageOptimization?

Type FunctionArgs | (args: FunctionArgs, opts: ComponentResourceOptions, name: string) => void

Transform the image optimization Function resource.

transform.server?

Type FunctionArgs | (args: FunctionArgs, opts: ComponentResourceOptions, name: string) => void

Transform the server Function resource.

vpc?

Type Vpc | Input<Object>

Configure the server function in your Remix app to connect to private subnets in a virtual private cloud or VPC. This allows your app to access private resources.

{
vpc: {
securityGroups: ["sg-0399348378a4c256c"],
subnets: ["subnet-0b6a2b73896dc8c4c", "subnet-021389ebee680c2f0"]
}
}

vpc.privateSubnets

Type Input<Input<string>[]>

A list of VPC subnet IDs.

vpc.securityGroups

Type Input<Input<string>[]>

A list of VPC security group IDs.

vpc.subnets?

Type Input<Input<string>[]>

A list of VPC subnet IDs.

warm?

Type Input<number>

Default 0

The number of instances of the server function to keep warm. This is useful for cases where you are experiencing long cold starts. The default is to not keep any instances warm.

This works by starting a serverless cron job to make n concurrent requests to the server function every few minutes. Where n is the number of instances to keep warm.

Properties

nodes

Type Object

The underlying resources this component creates.

nodes.assets

Type undefined | Bucket

The Amazon S3 Bucket that stores the assets.

nodes.cdn

Type undefined | Output<Cdn>

The Amazon CloudFront CDN that serves the app.

nodes.server

Type undefined | Output<Function>

The AWS Lambda server function that renders the site.

url

Type Output<string>

The URL of the Remix app.

If the domain is set, this is the URL with the custom domain. Otherwise, it’s the autogenerated CloudFront URL.

SDK

Use the SDK in your runtime to interact with your infrastructure.


This is accessible through the Resource object in the SDK.

  • url string

    The URL of the Remix app.

    If the domain is set, this is the URL with the custom domain. Otherwise, it’s the autogenerated CloudFront URL.