최근 클라우드 서비스를 이용한 마이크로 서비스 아키텍쳐(MSA)가 대두되고 있습니다.

AWS lambda는 MSA에 적합한 클라우드 서비스입니다.

이를 이용하면 간단한 코드 작성만으로 서버 없이 배포를 할 수 있습니다.

serverless 프레임워크는 node.js 환경에서 lambda 배포를 쉽게 할 수 있도록 돕습니다.

이 serverless를 이용해서 nuxt 서버 사이드 렌더링(SSR) 환경을 AWS lambda에 배포하는 방법을 알아보겠습니다.

1. Nuxt 프로젝트 생성

nuxt installation 문서create-nuxt-app을 참고해서 nuxt 프로젝트를 생성하겠습니다.

yarn create nuxt-app nuxt-lambda

또는

npx create-nuxt-app nuxt-lambda

npx를 이용하고자 할 경우 npm 5.2 버전 이상이 필요합니다.

저는 npm을 사용했습니다.

위 명령어 입력 후 프로젝트 이름(name)과 설명(description)을 입력하라는 안내에 따라 입력을 해줍니다.

그리고 Use a custom server framework?라는 안내가 나오는데, 서버 사이드 렌더링용 서버를 선택하라는 뜻입니다.

express를 선택하겠습니다.

aws-serverless-express라는 플러그인을 이용하면 쉽게 express 서버를 람다에 올릴 수 있습니다.

그 후는 UI 프레임워크, 테스트 도구, 린트 사용 여부 등을 묻는데 원하는대로 해도 무방합니다.

2. AWS credentials 설정

serverlessserverless.yml 파일을 읽어서 관련 아마존 웹 서비스들을 자동으로 생성, 수정합니다.

이를 하려면 serverless가 아마존 웹 서비스에 접근할 수 있는 IAM 권한을 부여해야 합니다.

serverless credentials 가이드 문서를 참고해서 설정 해보겠습니다.

IAM 서비스에 접속에서 왼쪽에 Users 메뉴를 클릭 후 Add User 버튼을 클릭합니다.

nuxt_lambda1.png

user name을 입력하라는 창이 나올텐데, 입력 후 next 버튼을 눌러줍니다.

nuxt_lambda2.png

그리고 Use a permissions boundary를 체크하고 Create policy 버튼을 클릭합니다.

nuxt_lambda3.png

JSON 탭을 선택 후 serverless credentials gist 문서에서 JSON을 복사해서 붙여넣고 Review policy 버튼을 눌러줍니다.

nuxt_lambda4.png

정책 이름을 잘 입력하고 Create policy버튼을 눌러 정책을 생성합니다. 저는 serverless라고 이름을 지었습니다.

nuxt_lambda5.png

그리고 유저 설정 탭으로 돌아와서 생성한 정책 이름으로 검색 후 체크하고 Next: Tags 버튼을 눌러줍니다.

nuxt_lambda6.png

계속 Next를 눌러서 진행하고 나면 유저 생성이 완료됩니다.

[default]
aws_access_key_id = Access key ID
aws_secret_access_key = Secret access key

그러고선 ~/.aws/credentials 파일을 생성해서 key ID와 key를 붙여넣으면 완료입니다.

3. Express 코드 작성

npm i serverless aws-serverless-express cross-env

코드를 작성하기 전 필요한 serverless, aws-serverless-express, cross-env을 설치하겠습니다.

이제 express 코드를 작성하겠습니다.

// 초기 server/index.js
const express = require('express')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = express()

// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')

async function start() {
  // Init Nuxt.js
  const nuxt = new Nuxt(config)

  const { host, port } = nuxt.options.server

  // Build only in dev mode
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }

  // Give nuxt middleware to express
  app.use(nuxt.render)

  // Listen the server
  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}
start()

위는 초기에 create-nuxt-app에서 생성해준 코드입니다.

serverless에서 쓰려면 appexport하는 코드로 변경해야합니다.

먼저 server/nuxt.js 파일을 분리하겠습니다.

// server/nuxt.js
const { Nuxt } = require('nuxt')
const nuxtConfig = require('../nuxt.config.js')

const nuxt = new Nuxt(nuxtConfig)

module.exports = {
  nuxt
}

그리고 server/index.jsserver/app.js 로 변경 후 다음과 같이 수정하겠습니다.

// server/app.js
const express = require('express')
const { nuxt } = require('./nuxt')

const app = express()

const BASE_URL = '/dev'
const REGEXP_BASE_URL = new RegExp(`^${BASE_URL}`)
const BASE_URL_TO_BE_ADDED = BASE_URL.replace(/\/$/, '')
const buildPath = (originalPath) => {
  if (REGEXP_BASE_URL.test(originalPath) === true) {
    return originalPath
  }
  return `${BASE_URL_TO_BE_ADDED}${originalPath}`
}

const envMiddleware = (req, res, next) => {
  const originalUrl = req.url
  const envUrl = buildPath(originalUrl)
  req.url = envUrl
  next()
}

app.use(envMiddleware)

app.use(async (req, res, next) => {
  await nuxt.ready()
  nuxt.render(req, res, next)
})

module.exports = app

nuxt 미들웨어를 사용하고, 서버를 바로 실행하는게 아닌 app을 내보내게 했습니다.

그래서 로컬 개발 환경에서 서버를 실행하려면 별도의 코드를 작성해야 합니다.

특이사항은 람다에 코드를 올리면 stage에 따라서 url/dev, /prod 등이 붙습니다.

이 때문에 별도로 처리하기 위해 envMiddleware 미들웨어를 사용했습니다.

이제 serverless에 필요한 handler.js를 작성하겠습니다.

// server/handler.js
const awsServerlessExpress = require('aws-serverless-express')
const app = require('./app')

const binaryMimeTypes = [
  'application/javascript',
  'application/json',
  'application/octet-stream',
  'application/xml',
  'font/eot',
  'font/opentype',
  'font/otf',
  'image/jpeg',
  'image/png',
  'image/svg+xml',
  'text/comma-separated-values',
  'text/css',
  'text/html',
  'text/javascript',
  'text/plain',
  'text/text',
  'text/xml'
]

const server = awsServerlessExpress.createServer(app, null, binaryMimeTypes)

module.exports.render = (event, context, callback) => {
  awsServerlessExpress.proxy(server, event, context)
}

binaryMimeTypesaws-serverless-express에 필요한 설정입니다. (사실 뭔지 잘 모릅니다..)

그리고 서버를 생성한 후 render 함수에 담아 내보냅니다. 이 함수가 최종적으로 람다에 올라가게 됩니다.

여기까지 하면 기본적인 서버 코드 작성이 완료됩니다.

4. nuxt.config.js 파일 수정

module.exports = {
  ...
  performance: {
    gzip: false
  },
  router: {
    base: '/dev'
  },
  ...
}

기본 설정에 performance와 router 옵션을 추가했습니다.

gzip의 경우 아직까지는 람다에서 지원하지 않아서 false로 했습니다.

람다에 올리게 될 경우 API gateway를 통해 웹페이지가 제공되는데, stage에 따라서 추가적으로 path가 붙습니다.

예를 들면 ashnamuh.comashnamuh.com/dev로 변합니다.

예제에선 dev stage로 올리므로 옵션을 추가했습니다.

실제로는 config 파일을 분리하고 stage에 맞게 동적으로 해야합니다.

5. serverless.yml 파일 작성

service: nuxt-lambda

provider:
  name: aws
  runtime: nodejs8.10 # node.js 런타임 버전
  stage: dev
  region: ap-northeast-2 # lambda, api gateway가 추가될 리전
  environment: # 환경 변수
    NODE_ENV: production

functions:
  render:
    handler: server/handler.render # server/handler.js의 render 함수를 가리킴
    events:
      - http:
          path: '/'
          method: get
      - http:
          path: '{proxy+}'
          method: get

package:
  excludeDevDependencies: true
  exclude: # 용량을 줄이기 위해서 static 폴더를 제외하고 불포함
    - .**
    - .**/*
    - pages/**
    - components/**
    - store/**
    - assets/**
    - layouts/**
    - plugins/**
    - test/**
    - middleware/**
    - README.md
    - package.json
    - package-lock.json
    - node_modules/**/*.md
    - node_modules/**/bin/**
  include:
    - server/**
    - config/index.js
    - config/config.js
    - nuxt.config.js
    - .nuxt/**
    - node_modules/autoprefixer/**
    - node_modules/babel-runtime/**
    - node_modules/babel-extract-comments/**

의외로 간단한 설정입니다.

람다는 최대 용량이 정해져 있어서 불필요한 파일은 제외했습니다.

그리고 node.js 런타임 함수 경로, 리전, stage 등을 설정했습니다.

6. package.json에 배포 명령어 작성

// package.json
...
"scripts": {
    "build": "nuxt build",
    "deploy": "npm run build && cross-env NODE_ENV=production sls deploy",
  },
...

nuxt buildproduction 환경으로 배포하는 명령어입니다.

7. 배포

이제 npm run deploy 명령어를 입력하면 serverless에서 serverless.yml 파일을 읽고 배포를 진행합니다.

nuxt_lambda7.jpeg

자동으로 api gatewaylambda 함수가 작성되었습니다.

이미 존재한다면 업데이트를 진행합니다.

배포 후 터미널에 나오는 endpoints url로 접근하시면 렌더링된 nuxt 페이지를 확인할 수 있습니다.

잘못된 점이 있으면 제보 부탁드립니다. 감사합니다.

생각해볼 것

  • 람다의 cold, warm 문제
  • 비용
  • 로컬 개발 환경에 필요한 express 코드 작성
  • 람다 stage에 따라 동적으로 nuxt.config.jsrouter 값 변경하기

예제 코드

예제 코드는 아래 링크에서 확인할 수 있습니다.

5분마다 람다 함수를 호출해서 cold, warm 문제를 플러그인으로 해결했고,

local 개발 환경 및 config 파일로 변수 관리하는 코드가 추가되어 있습니다.

https://github.com/ashnamuh/nuxt-lambda

참고한 링크

https://dev.to/adnanrahic/a-crash-course-on-serverless-side-rendering-with-vuejs-nuxtjs-and-aws-lambda-1nk4

https://medium.com/@fernalvarez/serverless-side-rendering-with-aws-lambda-nuxtjs-b94d15782af5

https://mya-ake.com/posts/nuxtjs-on-aws-lambda/