Remix com serverless

rfoel

Rafael Franco

Posted on November 12, 2022

Remix com serverless

Nessa postagem vamos aprender como fazer o deploy de uma aplicação Remix no serverless. Caso só queria ver o código, acesse esse link.

Ao criar uma nova aplicação Remix temos a possibilidade de escolher onde iremos subir nossa aplicação como Remix App Server, Express Server, Architect (AWS Lambda), Fly.io, Netlify, Vercel e Cloudflare Pages. Alguns desses não dão tanta margem pra escolha como Netlify e Vercel e outros requerem uma infraestrutura um pouco mais robusta para rodar como o Express Server. Pessoalmente eu prefiro ter controle total da minha aplicação ao invés de depender do dashboard do Vercel ou Netlify e das restrições que essas plataformas impõe, e mesmo que o Architect seja extremamente parecido com o que vamos aprender aqui, ele também tem suas próprias restrições e uma documentação não tão boa para iniciantes.

Por isso, ao conhecer o framework Remix eu fiquei curioso para saber se funcionaria em um ambiente serverless como o AWS Lambda, tendo total controle no deploy e sobre como a aplicação funciona. Procurando soluções já prontas me deparei com o repositório shamsup/remix-starter-serverless, que possui um README muito bem detalhado do processo e é basicamente isso que vamos resumir nessa postagem.

Para iniciar, podemos criar uma nova aplicação Remix usando o template do Architect (AWS Lambda), que possui uma configuração similar ao que vamos usar aqui.

npx create-remix@latest 
? Where would you like to create your app? remix-serverless
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Architect (AWS Lambda)
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes
💿 That's it! `cd` into "/Users/rafael/Developer/remix-serverless" and check the README for development and deploy instructions!
Enter fullscreen mode Exit fullscreen mode

Logo de cara podemos apagar alguns arquivos que não vamos utilizar como server.js, app.arc e server/config.arc. Também podemos apagar os scripts dev:arc e start do nosso package.json.

"scripts": {
  "build": "remix build",
  "dev:remix": "remix watch",
  "dev:arc": "cross-env NODE_ENV=development arc sandbox",
  "dev": "remix build && run-p \"dev:*\"",
  "start": "cross-env NODE_ENV=production arc sandbox"
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos instalar as dependências do serverless pra nossa configuração.

npm install serverless esbuild serverless-esbuild serverless-s3-sync -D
Enter fullscreen mode Exit fullscreen mode

No arquivo remix.config.js, vamos apagar o campo server e adicionar um novo chamado serverBuildDirectory.

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  serverBuildTarget: 'arc',
  ignoredRouteFiles: ['**/.*'],
  server: 'server.js',
  serverBuildDirectory: 'server/build',
  // appDirectory: "app",
  // assetsBuildDirectory: "public/build",
  // serverBuildPath: "server/index.js",
  // publicPath: "/_static/build/",
}
Enter fullscreen mode Exit fullscreen mode

Com as dependências instaladas e o arquivo de configuração atualizado, podemos criar dois novos arquivo chamados serverless.yml onde ficará a configuração da nossa aplicação e server/remix.ts onde ficará o nosso Lambda handler do Remix.

import { createRequestHandler } from '@remix-run/architect'
import type { ServerBuild } from '@remix-run/node'

const build = require('./build') as ServerBuild

export const handler = createRequestHandler({
  build,
  getLoadContext() {
    return {}
  },
})
Enter fullscreen mode Exit fullscreen mode

Esse arquivo será o handler da nossa Lambda e receberá todas as requisições do cliente.

Agora vamos ver o arquivo de configuração do serverless.

service: remix-serverless

frameworkVersion: '3'

plugins:
  - serverless-esbuild
  - serverless-s3-sync

provider:
  name: aws
  runtime: nodejs16.x
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}

custom:
  esbuild:
    bundle: true
    minify: false
    sourcemap: true
    sourcesContent: false
    exclude: ['aws-sdk']
    target: 'node16'
    platform: 'node'

  s3Sync:
    buckets:
      - bucketNameKey: WebsiteBucketName
        bucketPrefix: website/
        localDir: public

functions:
  remix:
    handler: server/remix.handler
    events:
      - httpApi:
          method: any
          path: '/{proxy+}'

resources:
  Resources:
    HttpApi:
      Type: AWS::ApiGatewayV2::Api
      Properties:
        Name: HttpApi-${self:service}
        ProtocolType: HTTP

    WebsiteBucket:
      Type: AWS::S3::Bucket
      Properties: {}

    WebsiteOriginAccessIdentity:
      Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
      Properties:
        CloudFrontOriginAccessIdentityConfig:
          Comment: Origin Access Identity to Access ${self:service} Website Bucket

    WebsiteBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref WebsiteBucket
        PolicyDocument:
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
              Resource:
                Fn::Join:
                  - /
                  - - Fn::GetAtt:
                        - WebsiteBucket
                        - Arn
                    - '*'
              Principal:
                CanonicalUser:
                  Fn::GetAtt:
                    - WebsiteOriginAccessIdentity
                    - S3CanonicalUserId

    CDN:
      Type: AWS::CloudFront::Distribution
      DependsOn:
        - WebsiteBucket
        - HttpApi
      Properties:
        DistributionConfig:
          Enabled: true
          Origins:
            - DomainName:
                Fn::GetAtt:
                  - WebsiteBucket
                  - RegionalDomainName
              Id: StaticOrigin
              S3OriginConfig:
                OriginAccessIdentity:
                  Fn::Join:
                    - /
                    - - origin-access-identity
                      - cloudfront
                      - !Ref WebsiteOriginAccessIdentity
              OriginPath: '/website'
            - DomainName:
                Fn::Join:
                  - ''
                  - - Ref: HttpApi
                    - '.execute-api.${self:provider.region}.amazonaws.com'
              Id: RemixOrigin
              CustomOriginConfig:
                OriginProtocolPolicy: https-only
                OriginSSLProtocols: [TLSv1.2]
          DefaultCacheBehavior:
            AllowedMethods: [GET, HEAD, OPTIONS, PUT, PATCH, POST, DELETE]
            CachedMethods: [GET, HEAD, OPTIONS]
            Compress: true
            TargetOriginId: RemixOrigin
            ViewerProtocolPolicy: redirect-to-https
            ForwardedValues:
              QueryString: true
              Cookies:
                Forward: none

  Outputs:
    WebsiteBucketName:
      Value:
        Ref: WebsiteBucket
    DistributionID:
      Value:
        Ref: CDN
    WebsiteDomain:
      Value:
        Fn::GetAtt: [CDN, DomainName]
Enter fullscreen mode Exit fullscreen mode

Esse é o arquivo de configuração padrão do serverless. Com isso, você terá o mínimo necessário para conseguir subir sua aplicação na AWS.

Alguns pontos interessantes dessa configuração é que além de um Lambda que recebe as requisições, também estamos usando o CloudFront como CDN e o S3 para armazenar nossos arquivos estáticos. Sobre esse último, usamos o plugin serverless-s3-sync para subir esses arquivos diretamente no bucket em todo deploy.

💖 💪 🙅 🚩
rfoel
Rafael Franco

Posted on November 12, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Remix com serverless
remix Remix com serverless

November 12, 2022