RemixSite
caution
This is the SST v1.x Constructs doc. SST v2 is now released. If you are using v2, see the v2 Constructs doc. If you are looking to upgrade to v2, check out the upgrade steps.
The RemixSite
construct is a higher level CDK construct that makes it easy to create a Remix app. It provides a simple way to build and deploy the app to AWS:
- The browser build and public static assets are deployed to an S3 Bucket, and served out from a CloudFront CDN for fast content delivery.
- The app server is deployed to Lambda. You can deploy to Lambda@Edge instead if the
edge
flag is enabled. Read more about Single region vs Edge. - It enables you to configure custom domains for the website URL.
- It also enable you to automatically set the environment variables for your Remix app directly from the outputs in your SST app.
- It provides a simple interface to grant permissions for your app to access AWS resources.
Quick Start
- If you are creating a new Remix app, run
create-remix
from the root of your SST app.
npx create-remix@latest
And select Remix App Server
as the deployment target.
After the Remix app is created, your SST app structure should look like:
my-sst-app
├─ sst.json
├─ services
├─ stacks
└─ my-remix-app <-- new Remix app
├─ app
├─ public
└─ remix.config.js
You can now jump to step 3 to complete the rest of the step.
- If you have an existing Remix app, move the app to the root of your SST app. Your SST app structure should look like:
my-sst-app
├─ sst.json
├─ services
├─ stacks
└─ my-remix-app <-- your Remix app
├─ app
├─ public
└─ remix.config.js
When you created your Remix app, you might've picked a different deployment target. We need to set the deploymen target to Remix App Server
. To do that, make sure your remix.config.js
contain the follow values.
module.exports = {
// ...
assetsBuildDirectory: "public/build",
publicPath: "/build/",
serverBuildPath: "build/index.js",
serverBuildTarget: "node-cjs",
server: undefined,
// ...
};
info
If you followed the Developer Blog
or Jokes App
tutorials on Remix's doc, it's likely you are using SQLite for database. SQLite databases cannot be deployed to a serverless environment. It is often used for local storage, and not recommended for modern web apps. It is recommended to use PostgreSQL, DynamoDB, or one of third party services like MongoDB for your database.
- Go into your Remix app, and add the
static-site-env
dependency to your Remix application'spackage.json
.static-site-env
enables you to automatically set the environment variables for your Remix app directly from the outputs in your SST app.
npm install --save-dev @serverless-stack/static-site-env
Update the package.json scripts for your Remix application.
"scripts": {
"build": "remix build",
- "dev": "remix dev",
+ "dev": "sst-env -- remix dev",
"start": "remix-serve build"
},
- Add the
RemixSite
construct to an existing stack in your SST app. You can also create a new stack for the app.
import { RemixSite, StackContext } from "@serverless-stack/resources";
export default function MyStack({ stack }: StackContext) {
// ... existing constructs
// Create the Remix site
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
});
// Add the site's URL to stack output
stack.addOutputs({
URL: site.url,
});
}
When you are building your SST app, RemixSite
will invoke npm build
inside the Remix app directory. Make sure path
is pointing to the your Remix app.
Note that we also added the site's URL to the stack output. After deploy succeeds, the URL will be printed out in the terminal.
Single region vs edge
There are two ways you can deploy the Remix app to your AWS account.
By default, the Remix app server is deployed to a single region defined in your sst.json
or passed in via the --region
flag. Alternatively, you can choose to deploy to the edge. When deployed to the edge, loaders/actions are running on edge location that is physically closer to the end user. In this case, the app server is deployed to AWS Lambda@Edge.
You can enable edge like this:
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
edge: true,
});
Note that, in the case you have a centralized database, Edge locations are often far away from your database. If you are quering your database in your loaders/actions, you might experience much longer latency when deployed to the edge.
info
We recommend you to deploy to a single region when unsure.
Custom domains
You can configure the website with a custom domain hosted either on Route 53 or externally.
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: "my-app.com",
});
Note that visitors to the http://
URL will be redirected to the https://
URL.
You can also configure an alias domain to point to the main domain. For example, to setup www.my-app.com
redirecting to my-app.com
:
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName: "my-app.com",
domainAlias: "www.my-app.com",
},
});
Environment variables
The RemixSite
construct allows you to set the environment variables in your Remix app based on outputs from other constructs in your SST app. So you don't have to hard code the config from your backend. Let's look at how.
To expose environment variables to your Remix application you should utilise the RemixSite
construct environment
configuration property rather than an .env
file within your Remix application root.
Imagine you have an API created using the Api
construct, and you want to fetch data from the API. You'd pass the API's endpoint to your Remix app.
const api = new Api(stack, "Api", {
// ...
});
new RemixSite(stack, "Site", {
path: "path/to/site",
environment: {
API_URL: api.url,
},
});
Then you can access the API's URL in your loaders/actions:
export async function loader() {
console.log(process.env.API_URL);
}
info
Remix only supports server environment variables. If you are looking to access environment variables in your browser code, follow the Remix guide on browser environment variables.
In our example, you'd return ENV
for the client from the root loader.
export async function loader() {
return json({
ENV: {
API_URL: process.env.API_URL,
},
});
}
Let's take look at what is happening behind the scene.
While deploying
On sst deploy
, the Remix app server is deployed to a Lambda function, and the RemixSite's environment
values are set as Lambda function environment variables. In this case, process.env.API_URL
will be available at runtime.
If you enabled the edge
option, the Remix app server will instead get deployed to a Lambda@Edge function. We have an issue here, AWS Lambda@Edge does not support runtime environment variables. To get around this limitation, we insert a snippet to the top of your app server:
const environment = "{{ _SST_REMIX_SITE_ENVIRONMENT_ }}";
process.env = { ...process.env, ...environment };
And at deploy time, after the referenced resources have been created, the API in this case, a CloudFormation custom resource will update the app server's code and replace the placeholder {{ _SST_REMIX_SITE_ENVIRONMENT_ }}
with the actual value:
const environment = {
API_URL: "https://ioe7hbv67f.execute-api.us-east-1.amazonaws.com",
};
process.env = { ...process.env, ...environment };
This will make process.env.API_URL
available at runtime.
While developing
To use these values while developing, run sst start
to start the Live Lambda Development environment.
npx sst start
Then in your Remix app to reference these variables, add the static-site-env
package.
npm install --save-dev @serverless-stack/static-site-env
And tweak the Remix dev
script to:
"scripts": {
"build": "remix build",
"dev": "sst-env -- remix dev",
"start": "remix-serve build"
},
Now you can start your Remix app as usual and it'll have the environment variables from your SST app.
npm run dev
There are a couple of things happening behind the scenes here:
- The
sst start
command generates a file with the values specified by theRemixSite
construct'senvironment
prop. - The
sst-env
CLI will traverse up the directories to look for the root of your SST app. - It'll then find the file that's generated in step 1.
- It'll load these as environment variables before running the start command.
note
sst-env
only works if the Remix app is located inside the SST app or inside one of its subdirectories. For example:
/
sst.json
my-remix-app/
Using AWS services
Since the RemixSite
construct deploys your Remix app to your AWS account, it's very convenient to access other resources in your AWS account in your Remix loaders/actions. RemixSite
provides a simple way to grant permissions to access specific AWS resources.
Imagine you have a DynamoDB table created using the Table
construct, and you want to fetch data from the Table.
const table = new Table(stack, "Table", {
// ...
});
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
environment: {
TABLE_NAME: table.tableName,
},
});
site.attachPermissions([table]);
Note that we are also passing the table name into the environment, so the Remix loaders/actions can fetch the value process.env.TABLE_NAME
when calling the DynamoDB API to query the table.
Examples
Configuring custom domains
You can configure the website with a custom domain hosted either on Route 53 or externally.
Using the basic config (Route 53 domains)
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: "my-app.com",
});
Redirect www to non-www (Route 53 domains)
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName: "my-app.com",
domainAlias: "www.my-app.com",
},
});
Configuring domains across stages (Route 53 domains)
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName:
scope.stage === "prod" ? "my-app.com" : `${scope.stage}.my-app.com`,
domainAlias: scope.stage === "prod" ? "www.my-app.com" : undefined,
},
});
Configuring alternate domain names (Route 53 domains)
You can specify additional domain names for the site url. Note that the certificate for these names will not be automatically generated, so the certificate option must be specified. Also note that you need to manually create the Route 53 records for the alternate domain names.
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
// Look up hosted zone
const hostedZone = route53.HostedZone.fromLookup(stack, "HostedZone", {
domainName: "domain.com",
});
// Create a certificate with alternate domain names
const certificate = new acm.DnsValidatedCertificate(stack, "Certificate", {
domainName: "foo.domain.com",
hostedZone,
region: "us-east-1",
subjectAlternativeNames: ["bar.domain.com"],
});
// Create site
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName: "foo.domain.com",
alternateNames: ["bar.domain.com"],
cdk: {
hostedZone,
certificate,
},
},
});
// Create A and AAAA records for the alternate domain names
const recordProps = {
recordName: "bar.domain.com",
zone: hostedZone,
target: route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(site.cdk.distribution)
),
};
new route53.ARecord(stack, "AlternateARecord", recordProps);
new route53.AaaaRecord(stack, "AlternateAAAARecord", recordProps);
Importing an existing certificate (Route 53 domains)
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName: "my-app.com",
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});
Note that, the certificate needs be created in the us-east-1
(N. Virginia) region as required by AWS CloudFront.
Specifying a hosted zone (Route 53 domains)
If you have multiple hosted zones for a given domain, you can choose the one you want to use to configure the domain.
import { HostedZone } from "aws-cdk-lib/aws-route53";
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
domainName: "my-app.com",
cdk: {
hostedZone: HostedZone.fromHostedZoneAttributes(stack, "MyZone", {
hostedZoneId,
zoneName,
}),
},
},
});
Configuring externally hosted domain
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
new RemixSite(stack, "Site", {
path: "my-remix-app/",
customDomain: {
isExternalDomain: true,
domainName: "my-app.com",
cdk: {
certificate: Certificate.fromCertificateArn(stack, "MyCert", certArn),
},
},
});
Note that the certificate needs be created in the us-east-1
(N. Virginia) region as required by AWS CloudFront, and validated. After the Distribution
has been created, create a CNAME DNS record for your domain name with the Distribution's
URL as the value. Here are more details on configuring SSL Certificate on externally hosted domains.
Also note that you can also migrate externally hosted domains to Route 53 by following this guide.
Configuring the Lambda Function
Configure the internally created CDK Lambda Function
instance.
new RemixSite(stack, "Site", {
path: "my-remix-app/",
defaults: {
function: {
timeout: 20,
memorySize: 2048,
permissions: ["sns"],
},
},
});
Advanced examples
Using an existing S3 Bucket
import * as s3 from "aws-cdk-lib/aws-s3";
new RemixSite(stack, "Site", {
path: "my-remix-app/",
cdk: {
bucket: s3.Bucket.fromBucketName(stack, "Bucket", "my-bucket"),
},
});
Reusing CloudFront cache policies
CloudFront has a limit of 20 cache policies per AWS account. This is a hard limit, and cannot be increased. Each RemixSite
creates 3 cache policies. If you plan to deploy multiple Remix sites, you can have the constructs share the same cache policies by reusing them across sites.
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
const cachePolicies = {
browserBuildCachePolicy: new cloudfront.CachePolicy(
stack,
"BrowserBuildStaticsCache",
RemixSite.browserBuildCachePolicyProps
),
publicCachePolicy: new cloudfront.CachePolicy(
stack,
"PublicStaticsCache",
RemixSite.publicCachePolicyProps
),
serverResponseCachePolicy: new cloudfront.CachePolicy(
stack,
"ServerResponseCache",
RemixSite.serverResponseCachePolicyProps
),
};
new RemixSite(stack, "Site1", {
path: "my-remix-app/",
cdk: {
cachePolicies,
},
});
new RemixSite(stack, "Site2", {
path: "another-remix-app/",
cdk: {
cachePolicies,
},
});
Protecting server function behind API Gateway
When deployed to a single region, instead of sending the request to the server function directly, you can send the request to API Gateway and have API Gateway proxy the request to the server function. With this setup, you can use features like authorizers to protect the server function.
import { Fn } from "aws-cdk-lib";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
// Create an API Gateway API
const api = new Api(stack, "Api");
// Configure the CloudFront distribution to route requests to the API endpoint
const site = new RemixSite(stack, "Site", {
path: "my-remix-app/",
cdk: {
distribution: {
defaultBehavior: {
origin: new origins.HttpOrigin(Fn.parseDomainName(api.url)),
},
},
},
});
// Configure the API Gateway to route all incoming requests to the site's SSR function
// Note: The site is not deployed when using the `sst dev` command
if (!app.local) {
api.addRoutes(stack, {
"ANY /{proxy+}": {
type: "function",
cdk: {
function: site.cdk.function,
},
},
});
}
Constructor
new RemixSite(scope, id, props)
Parameters
- scope Construct
- id string
- props RemixSiteProps
RemixSiteProps
customDomain?
Type : string | RemixDomainProps
The customDomain for this website. SST supports domains that are hosted either on Route 53 or externally. Note that you can also migrate externally hosted domains to Route 53 by following this guide.
new RemixSite(stack, "RemixSite", {
path: "path/to/site",
customDomain: "domain.com",
});
new RemixSite(stack, "RemixSite", {
path: "path/to/site",
customDomain: {
domainName: "domain.com",
domainAlias: "www.domain.com",
hostedZone: "domain.com"
},
});
defaults.function.memorySize?
Type : number
defaults.function.permissions?
Type : Permissions
defaults.function.timeout?
Type : number
disablePlaceholder?
Type : boolean
When running sst start
, a placeholder site is deployed. This is to ensure
that the site content remains unchanged, and subsequent sst start
can
start up quickly.
new RemixSite(stack, "RemixSite", {
path: "path/to/site",
disablePlaceholder: true,
});
edge?
Type : boolean
Default : false
The Remix app server is deployed to a Lambda function in a single region. Alternatively, you can enable this option to deploy to Lambda@Edge.
environment?
Type : Record<string, string>
An object with the key being the environment variable name.
new RemixSite(stack, "RemixSite", {
path: "path/to/site",
environment: {
API_URL: api.url,
USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
},
});
path
Type : string
Path to the directory where the website source is located.
waitForInvalidation?
Type : boolean
While deploying, SST waits for the CloudFront cache invalidation process to finish. This ensures that the new content will be served once the deploy command finishes. However, this process can sometimes take more than 5 mins. For non-prod environments it might make sense to pass in false
. That'll skip waiting for the cache to invalidate and speed up the deploy process.
cdk.bucket?
Type : IBucket | BucketProps
Allows you to override default settings this construct uses internally to ceate the bucket
cdk.cachePolicies.buildCachePolicy?
Type : ICachePolicy
Override the CloudFront cache policy properties for browser build files.
cdk.cachePolicies.serverCachePolicy?
Type : ICachePolicy
Override the CloudFront cache policy properties for responses from the server rendering Lambda.
The default cache policy that is used in the abscene of this property is one that performs no caching of the server response.
cdk.cachePolicies.staticsCachePolicy?
Type : ICachePolicy
Override the CloudFront cache policy properties for "public" folder
static files.
Note: This will not include the browser build files, which have a seperate
cache policy; @see buildCachePolicy
.
Override the default CloudFront cache policies created internally.
cdk.distribution?
Type : RemixCdkDistributionProps
Pass in a value to override the default settings this construct uses to
create the CDK Distribution
internally.
cdk.id?
Type : string
Allows you to override default id for this construct.
Properties
An instance of RemixSite
has the following properties.
bucketArn
Type : string
The ARN of the internally created S3 Bucket.
bucketName
Type : string
The name of the internally created S3 Bucket.
buildCachePolicyProps
Type : CachePolicyProps
The default CloudFront cache policy properties for browser build files.
customDomainUrl
Type : undefined | string
If the custom domain is enabled, this is the URL of the website with the custom domain.
distributionDomain
Type : string
The domain name of the internally created CloudFront Distribution.
distributionId
Type : string
The ID of the internally created CloudFront Distribution.
id
Type : string
serverCachePolicyProps
Type : CachePolicyProps
The default CloudFront cache policy properties for responses from the server rendering Lambda.
By default no caching is performed on the server rendering Lambda response.
staticsCachePolicyProps
Type : CachePolicyProps
The default CloudFront cache policy properties for "public" folder static files.
This policy is not applied to the browser build files; they have a seperate
cache policy; @see buildCachePolicyProps
.
url
Type : string
The CloudFront URL of the website.
cdk.bucket
Type : Bucket
The internally created CDK Bucket
instance.
cdk.certificate?
Type : ICertificate
The AWS Certificate Manager certificate for the custom domain.
cdk.distribution
Type : Distribution
The internally created CDK Distribution
instance.
cdk.function?
Type : Function
The internally created CDK Function
instance. Not available in the "edge" mode.
cdk.hostedZone?
Type : IHostedZone
The Route 53 hosted zone for the custom domain.
Exposes CDK instances created within the construct.
Methods
An instance of RemixSite
has the following methods.
attachPermissions
attachPermissions(permissions)
Parameters
- permissions Permissions
Attaches the given list of permissions to allow the Remix server side rendering to access other AWS resources.
const site = new RemixSite(stack, "Site", {
path: "path/to/site",
});
site.attachPermissions(["sns"]);
RemixDomainProps
alternateNames?
Type : Array<string>
Default : []
Specify additional names that should route to the Cloudfront Distribution. Note, certificates for these names will not be automatically generated so the certificate
option must be specified.
domainAlias?
Type : string
Default : no alias configured
An alternative domain to be assigned to the website URL. Visitors to the alias will be redirected to the main domain. (ie. www.domain.com
).
Use this to create a www.
version of your domain and redirect visitors to the root domain.
domainName
Type : string
The domain to be assigned to the website URL (ie. domain.com). Supports domains that are hosted either on Route 53 or externally.
hostedZone?
Type : string
Default : same as the domainName
The hosted zone in Route 53 that contains the domain. By default, SST will look for a hosted zone matching the domainName that's passed in. Set this option if SST cannot find the hosted zone in Route 53.
isExternalDomain?
Type : boolean
Default : false
Set this option if the domain is not hosted on Amazon Route 53.
cdk.certificate?
Type : ICertificate
Import the certificate for the domain. By default, SST will create a certificate with the domain name. The certificate will be created in the us-east-1
(N. Virginia) region as required by AWS CloudFront.
Set this option if you have an existing certificate in the us-east-1
region in AWS Certificate Manager you want to use.
cdk.hostedZone?
Type : IHostedZone
Import the underlying Route 53 hosted zone.
RemixCdkDistributionProps
defaultBehavior?
Type :