Remix com serverless
Tags: remix, serverless, react
Published: 2022-11-12 14:26:12

---
title: Remix com serverless
published: true
description: Nessa postagem vamos aprender como fazer o deploy de uma aplicação Remix no serverless.
tags: remix, serverless, react
cover_image: https://user-images.githubusercontent.com/19496473/197066773-95e1163f-42a1-47f7-ace1-4eafae33caa9.png
published_at: 2022-10-20 20:55 +3000
---

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](https://github.com/rfoel/remix-serverless).

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](https://github.com/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.

```sh
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!
```

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`.

```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"
}
```

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

```sh
npm install serverless esbuild serverless-esbuild serverless-s3-sync -D
```

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

```js
/** @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/",
}
```

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.

```ts 
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 {}
  },
})
```

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.

```yaml 
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]
```

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.