The AWS Amplify team recently announced the Amplify Hosting deployment specification. This is a way to deploy applications to AWS Amplify with server side rendering (SSR) support. While there are guides or support for many frameworks including Astro, NextJS and Nuxt I couldn't find one for Remix.
As I'm about to start working on a Shopify app (Shopify provides templates for Remix) and I didn't want to switch infrastructure providers I decided to investigate using how difficult it would be to use Amplify to host a Remix app with SSR.
The first step is to create a build file named amplify.yml
which Amplify uses to build the project. I was deploying a new project and Amplify detected the file automatically during the initial deployment. Our build script handles four important tasks:
Runs
npm run build
to build the application. Remix puts the output in into thebuild
folder.Moves and restuctures the
build
folder output to meet the Amplify hosting specification byRenaming
build
to.amplify-hosting
Renaming the
client
subfolder tostatic
Moving the
server
subfolder tocompute/default
Reduce the size of our
node-modules
folder by runningnpm ci --omit dev
to create anode-modules
that only includes production dependencies. This is important for larger project as there is a limit on the maximum size of this folder. It also helps reduce cold start times. The folder is moved into thecompute/default
folder so the modules are available at runtime.Finally there are two files that we will create shortly that need to be copied into place.
The
server.js
goes into.amplify-hosting/compute/default
The
deploy-manifest.json
goes into.amplify-hosting/deploy-manifest.json
The complete amplify.yml
is below:
version: 1
baseDirectory: .amplify-hosting
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
- mv build .amplify-hosting
- mv .amplify-hosting/client .amplify-hosting/static
- mkdir -p .amplify-hosting/compute
- mv .amplify-hosting/server .amplify-hosting/compute/default
- npm ci --omit dev
- cp package.json .amplify-hosting/compute/default
- cp -r node_modules .amplify-hosting/compute/default
- cp server.js .amplify-hosting/compute/default
- cp deploy-manifest.json .amplify-hosting/deploy-manifest.json
artifacts:
files:
- "**/*"
baseDirectory: .amplify-hosting
AWS Amplify will now package and deploy our .amplify-hosting
folder.
The deploy-mainfest.json
has two main tasks:
Tell Amplify how to route traffic
Tell Amplify how to configure and start the compute resources
This routing should not be confused with rewriting and redirecting traffic. It's purpose is to indicate if traffic should be handled as static or compute. Here I'm using the less than perfect approach that files with a .
should be treated as static first and fall back to compute if they don't exist. Everything else should be treated as compute. This means that static files must have a .
in the filename.
For the compute configuration I've set the runtime to Node 20 and the project should be started by using node server.js
The full deploly-mainfest.json
is:
{
"version": 1,
"framework": {
"name": "remix",
"version": "2.8.1"
},
"routes": [
{
"path": "/*.*",
"target": {
"kind": "Static",
"cacheControl": "public, max-age=2"
},
"fallback": {
"kind": "Compute",
"src": "default"
}
},
{
"path": "/*",
"target": {
"kind": "Compute",
"src": "default"
}
}
],
"computeResources": [
{
"name": "default",
"runtime": "nodejs20.x",
"entrypoint": "server.js"
}
]
}
Finally I need to create a small Javascript file called server.js
. This file launches an Express server on port 3000 which listens for requests and passes them to Remix. Remember to add express as a production dependency so this will work.
npm i express --save
The complete server.js is:
import remix from "@remix-run/express";
import express from "express";
import * as build from "./index.js";
const app = express();
const port = 3000;
app.all(
"*",
remix.createRequestHandler({
build,
})
);
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
That's it! You should be able to deploy the project to AWS Amplify and when you go to the URL provided your request will be execute on the server.