New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add RemixSite construct #1800
Conversation
ea431b6
to
30626d7
Compare
Keeping track a list of threads looking for the Remix construct: |
f20a12c
to
cbcd939
Compare
9c0f95c
to
f52036c
Compare
Just want to say I appreciate the insane amount of work you are putting into this PR. |
This is amazing. Really looking forward to using it in SST. I currently use a non-SST CDK based stack for my Remix stuff. |
f5c0990
to
b892667
Compare
b892667
to
d3a4b3f
Compare
packages/resources/assets/RemixSite/config/read-remix-config.cjs
Outdated
Show resolved
Hide resolved
a7015bd
to
d029a70
Compare
I had a look at the network tab, and it doesn't appear that caching is working as expected. I am getting misses on the CF cache, and the Update: spoke too soon. The "build" folder is being cached in CF, although the "Cache-Control" header isn't being returned in the response. So it appears only the favicon.ico isn't being cached in CF. I'll try add another file to the public folder and see if it this holds true to all public statics. This may just be a special case for favicon.ico. Update2: Yeah, just some general inconsistencies with the caching policies. Doing some experiments. Updated3: Solved by the below commits and verified in my network panel ✅ |
705fbb3
to
860eb2a
Compare
@ctrlplusb I removed the function alias, let me know if that causes any issue for you. I still have some work around the docs for tmr. Apart from that, I think the code is ready! |
51cec3d
to
346dc02
Compare
Rebased. FYI. |
Very excited to see this get in. Is there anything I can do to help this get finished up? |
Will be launching tmr or the day after :) |
Released in 1.4.0 Hooray! |
RemixSite
This PR adds a
RemixSite
construct to support deployment of Remix applications. The construct supports multiple deployment models for the SSR Lambda; Lambda@Edge or APIGatewayV2 Lambda.What is Remix?
From their homepage;
Motivation
I've only recently dug into Remix, and haven't used it in production, but I must say that my limited experience of it has already made it seem far more appealing to me than Next.js. There is far less to think about. It only has a single page rendering model - server side. This results in a much reduced cognitive overhead compared to Next.js. In addition to this they are leaning heavily into the standard APIs and practices of the Web platform. Overall I feel like they are pushing us into the right direction. I've additionally found it really easy to mentally model the difference between server and client code execution. Their
.server.ts
filename postfix strategy, whilst simple, is really effective at helping maintain the distinction.From what I have read from their team they appear to favour edge based deployments. Whilst they already have an AWS Lambda compatible build - via their official Architect template. This deployment only supports an API Gateway based Lambda for SSR. In addition to this I am unsure if they are utilising CloudFront. There is a strong opportunity here for us to create an alternative AWS deployment that is more in line with their ideals.
Given this opportunity I believe this work could open the door to us having a conversation with the Remix team, and hopefully convince them to integrate an official Serverless Stack based template.
Whilst this initial implementation is meant to execute against "Vanilla" Remix application configurations that are created within an SST based project, I am designing the construct in a manner that we could support an official template solution within the Remix repository. I invite some discourse on this point as we evolve this implementation.
I believe that should we be integrated as an official template within Remix it would be a significant net gain for the Serverless Stack community, one that could hopefully drive increased adoption. There is definitely a hype train running against Remix, let's jump onboard!
Prerequisite Knowledge
Before being able to evaluate this PR it is important to have an understanding of some of the aspects of Remix which directly impacted the design of this construct.
"Vanilla" Remix Server
The
remix.config.js
exposes a configuration value calledserverBuildTarget
. When this value is not explicitly set it defaults tonode-cjs
. Executing theremix build
command withserverBuildTarget
beingnode-cjs
will not result an executable Remix server being output. It instead outputs a "core server build" module which contains the following exports;The "core server build" enables you to write your own server implementation that would handle the server rendering of Remix in a manner you require.
For example you could write a middleware for Express that consumes this module. This does require that you understand the exposed data and API from the "core server build" module. The Remix team have provided packages to aid in this development. For example, the
@remix-run/node
package.In terms of Express middleware, Remix already provides an implementation via their
@remix-run/express
package which is able to receive the "core server build" module and output an appropriate Express middleware.You would then need to add a
server.ts
file to your Remix application, in which you bootstrap an Express server and attach the middleware. Below is a minimal example of this.I very much recommend that you create a new vanilla Remix application with the following steps in order to build a deeper intuition about how what has been described above;
Create a new Remix project, utilising the "Remix App Server" option;
When the template selection is displayed, select the "Remix App Server" option. This will result in the "core server build" being output when the
remix build
command is executed.Run the build for the application;
You will note that the following folders were output as a result of the build;
/build
This folder contains the "core server build" as previously described.
I'd encourage you to view the contents of the
index.js
file contained within this directory. At the bottom of the file you will see the "core server build" exports previously discussed./public/build
This folder contains the "React client build", intended for use within the browser.
Install the official Express package which enables you to create a Remix compatible middleware;
Create a
server.js
file at the root of the project with the contents of the example shown previously within this section. 👆👆👆 (scroll up)Now we'll replace the Remix App Server with our Express server. Edit the package.json scripts to look like the following;
Start your Express server;
Open your browser to the following URL;
http://localhost:3000
You should see the Remix server response. 😎
Writing your own Remix server implementation clearly involves a bit of boilerplate, and understanding of the build output and APIs. Remix provides official templates to encapsulate this boilerplate for a variety of server models.
One of these templates is an "Express Server" template. You can select this template when bootstrapping a Remix application via their official
create-remix
CLI package, ensuring that the "Express Server" option is selected;The template contains a more robust implementation of the server.ts, along with a package.json scripts configuration which enables you to run your Express server both in development and production. I invite you to compare their "Express Server" template with the implementation created in the steps above.
The "Architect (AWS Lambda)" Template
Remix provides multiple templates to help you avoid writing boilerplate for your server deployment environment. These are selected when initializing your application via the
create-remix
CLI package;Each of these templates will bootstrap your Remix application with the required configuration, boilerplate files,
package.json
scripts, and NPM packages to support developing, building, and deploying Remix against the target environment.Probably of greatest interest to us is the "Architect (AWS Lambda)" template. I'll review this template in more detail to give a more clear understanding of what was required to provide first class support within Remix.
Bootstrapping a Remix application with the "Architect (AWS Lambda)" template produces the following file structure;
As you can see it contains an
app.arc
file, which is specific to Architect. It other Architect specific implementation. It additionally has aserver.js
file at the root, which contains the following:Note how it imports the
@remix-run/architect
package. This is an official Remix package which enables you to create an API Gateway Lambda compatible handler against the "core server build".The "core server build" isn't being imported directly, like in our Express server example. Instead it is being imported via the following line of code;
This acts as a token for the Remix compiler to know that it should replace the import with the contents of the "core server build". This additionally ensure TypeScript support is provided for the build, as they also include typing definitions within the
@remix-run/dev
package for this "dummy" module.The file is consumed by the Remix compiler via the
server
configuration property within theremix.config.js
file;When this configuration value is set the
remix build
command will bundle the contents of the file with the "core server build", performing the swap of theimport
statement with the actual "core server build" module.The output of the build is an API Gateway Lambda handler which is able to perform the Remix server rendering logic of your Remix application.
Static Assets
Remix supports static assets via some convention. Anything put into the "public" folder should be served relative to the root of the domain when deployed.
The React client side code is bundled by default into the "public/build" folder (this is created when executing the
remix build
command).Whilst this is considered the default behaviour it is possible to override the path at which the React client side code is output, as well as the public path (relative to the root of the domain) at which it should be considered hosted. The defaults for these are shown below;
Server Building
Building your Remix application is performed via their CLI;
Remix differs from other solutions, like Next.js, in that it doesn't expose the ability to override or extend the underlying tool they use to bundle your application.
The only customisation they offer around this is the ability to declare which dependencies should be bundled into your server build. By default the Remix build does not bundle any 3rd party dependencies into the server build.
In addition to this Remix has limited support for modern CSS solutions, and the team appears to favour an approach of manually adding a Tailwind based configuration to your application.
It is therefore highly likely that executing the
remix build
command in isolation might not produce all the output required for a deployment. Developers may have enhanced their "build" script within thepackage.json
to additionally call the Tailwind compiler, for instance.Solution Overview
This PR adds a
RemixSite
construct, enabling the deployment of "Vanilla" Remix applications (i.e. Remix applications that were bootstrapped with the "Remix App Server" option) to AWS.It supports two deployment models for the SSR Lambda; via CloudFront@Edge or APIGatewayV2 Lambda. Both models utilise a CloudFront Distribution, where the SSR is performed against an APIGatewayV2 Lambda we configure a custom origin source on the CloudFront Distribution to direct traffic accordingly.
Toggling between the two models is performed via an
edge
prop on the construct;The construct expects that you initialised a "Vanilla" Remix application via the "Remix, i.e. selecting the "Remix App Server" template;
The solution does not require any additional configuration or boilerplate (except for doing the standard
sst-env --
prefix on the "dev" script).Similar to some of the other templates being exposed by Remix we will rely on conventions in regards to the Remix configuration. We expect that the following properties within the
remix.config.js
have their "default" values;This provides with a simpler Construct in that we can assume the same build output structure by the Remix application. To ensure these values are the expected, the construct will perform an assertion against them. We output a helpful error message in the case that we identify an invalid
remix.config.js
configuration.To support a Lambda@Edge server, we have an internal
server.js
template file (seepackages/resources/assets/RemixSite/server/server-template.js
). The construct will inject the template into the Remix application build output, and then perform an ESBuild against it to create a fully bundled version of the server. This is performed during the synth process.Users can continue to use the Remix dev server, via
remix dev
, to perform local development of their Remix applications, utilising thesst-env
tool to inject their stack's environment variables. During deployment we utilise the same strategy that the Next.js site utilises to deploy the Lambda@Edge, whilst also performing the environment variable injection.The construct also enables deployment of the Remix application's static assets. It provides thre separate CloudFront cache policies;
Overall the
RemixSite
construct is simpler than theNextjsSite
construct. Remix doesn't provide as many features as Next.js - I believe a core principle of their design is to keep things "simple", recycling the same core aspect to support various use cases. This makes incorporating Remix within SST a more appealing aspect as we won't have to play as much "catch up" work compared to Next.js.I have tested deployments of the construct via the use of Yalc. Thusfar it is working really well, although it suffers the same issue as the
NextjsSite
construct when trying to delete a Lambda@Edge that has already been distributed in the network. It would be great to have a strategy to deal with this case.I have additionally added tests, although I feel these should be expanded to cover more cases, and I have updated the
www
documentation site;Todo
www
documentation for construct.Incorporate recent changes that have been performed againstNextjsSite
edge
flag