AWS stands for Amazon Web Services.
S3 is their static storage which can be configured for Static Site Hosting. CloudFront is their CDN (content delivery network)

AWS w/ the Amplify Console

Hosting a static generated Nuxt app on AWS w/ the Amplify Console is powerful and cheap.

First, push your Nuxt app to the Git provider of your choice. Then, visit the Amplify Console. Click the GET STARTED button under the Deploy header if you haven’t used Amplify Hosting before, otherwise click the Connect App button.

From your existing code

On the “From your existing code” page, select your Git provider and click Continue.

Add repository branch

On the “Add repository branch” page, select your repository and the branch you want to deploy. Then, click Next.

Configure build settings

On the “Configure build settings” page, click the Edit button under the “Build and test settings”. Change the following:

  1. Set the build commands to npm run generate.
  2. Set the baseDirectory location to be dist.

The settings should look like this once you are done editing them:

  1. version: 1
  2. frontend:
  3. phases:
  4. preBuild:
  5. commands:
  6. - yarn install
  7. build:
  8. commands:
  9. - npm run generate
  10. artifacts:
  11. # IMPORTANT - Please verify your build output directory
  12. baseDirectory: dist
  13. files:
  14. - '**/*'
  15. cache:
  16. paths:
  17. - node_modules/**/*

Then, click Save and Next.

Review

On the review page, click Save and deploy.

Then, your application will deploy. This may take a few minutes.

Once Provision, Build, Deploy, and Verify are green, click on the URL that the Amplify Console provides to view your site.

AWS w/ S3 + CloudFront

Hosting a static generated Nuxt app on AWS w/ S3 + CloudFront is powerful and cheap.

AWS is a death by 1000 paper cuts. If we missed a step, please submit a PR to update this document.

Overview

We’ll host super cheap with some AWS services. Briefly:

  • S3
    • cloud data “bucket” for our website files
    • can be configured to host static websites
  • CloudFront
    • a CDN (content delivery network)
    • offers free HTTPS certs
    • Makes your site load faster

We’ll push the site like this:

  1. Nuxt Generate -> Local folder -> AWS S3 Bucket -> AWS CloudFront CDN -> Browser
  2. [ nuxt generate ] [ gulp deploy ]
  3. [ deploy.sh ]

First, we’ll generate the site with nuxt generate(<= v2.12). Then, we’ll use Gulp to publish the files to a S3 bucket and invalidate a CloudFront CDN.

Our deploy script needs these environment variables set:

  • AWS_BUCKET_NAME=”example.com”
  • AWS_CLOUDFRONT=”UPPERCASE”
  • AWS_ACCESS_KEY_ID=”key”
  • AWS_SECRET_ACCESS_KEY=”secret”

We’ll have these files:

  1. deploy.sh - run `nuxt generate` and `gulp deploy`
  2. gulpfile.js - `gulp deploy` code to push files to S3 and invalidate CloudFront

Setting it up

  1. Make a S3 bucket and configure it for static site hosting
  2. Create a CloudFront distribution
  3. Configure security access
  4. Setup build script in your project

AWS: Setup your S3 bucket and CloudFront Distribution

Please follow this tutorial to setup your S3 and CloudFront for step one and two.

You should now have this data:

  • AWS_BUCKET_NAME=”example.com”
  • AWS_CLOUDFRONT=”UPPERCASE”

AWS: Configure security access

For step 3, we need to create a user that can:

  • Update the bucket contents
  • Invalidate the CloudFront distribution (propagates changes to users faster)

Create a programmatic user with this policy:

NOTE: replace 2x example.com with your S3 bucket name below. This policy allows pushing to the specified bucket, and invalidating any CloudFront distribution.

  1. {
  2. "Version": "2012-10-17",
  3. "Statement": [
  4. {
  5. "Effect": "Allow",
  6. "Action": ["s3:ListBucket"],
  7. "Resource": ["arn:aws:s3:::example.com"]
  8. },
  9. {
  10. "Effect": "Allow",
  11. "Action": [
  12. "s3:PutObject",
  13. "s3:PutObjectAcl",
  14. "s3:GetObject",
  15. "s3:GetObjectAcl",
  16. "s3:DeleteObject",
  17. "s3:ListMultipartUploadParts",
  18. "s3:AbortMultipartUpload"
  19. ],
  20. "Resource": ["arn:aws:s3:::example.com/*"]
  21. },
  22. {
  23. "Effect": "Allow",
  24. "Action": [
  25. "cloudfront:CreateInvalidation",
  26. "cloudfront:GetInvalidation",
  27. "cloudfront:ListInvalidations",
  28. "cloudfront:UnknownOperation"
  29. ],
  30. "Resource": "*"
  31. }
  32. ]
  33. }

Then get an access key and secret.

You should now have this data:

  • AWS_ACCESS_KEY_ID=”key”
  • AWS_SECRET_ACCESS_KEY=”secret”

Laptop: Setup your project’s build script

4.1) Create a deploy.sh script. See optional nvm (node version manager).

  1. #!/bin/bash
  2. export AWS_ACCESS_KEY_ID="key"
  3. export AWS_SECRET_ACCESS_KEY="secret"
  4. export AWS_BUCKET_NAME="example.com"
  5. export AWS_CLOUDFRONT="UPPERCASE"
  6. # Load nvm (node version manager), install node (version in .nvmrc), and npm install packages
  7. [ -s "$HOME/.nvm/nvm.sh" ] && source "$HOME/.nvm/nvm.sh" && nvm use
  8. # Npm install if not already.
  9. [ ! -d "node_modules" ] && npm install
  10. npm run generate
  11. gulp deploy

4.2) Make deploy.sh runnable and DON’T CHECK INTO GIT (deploy.sh has secrets in it)

  1. chmod +x deploy.sh
  2. echo "
  3. # Don't commit build files
  4. node_modules
  5. dist
  6. .nuxt
  7. .awspublish
  8. deploy.sh
  9. " >> .gitignore

4.3) Add Gulp to your project and to your command line

  1. npm install --save-dev gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform
  2. npm install -g gulp

4.4) Create a gulpfile.js with the build script

  1. const gulp = require('gulp')
  2. const awspublish = require('gulp-awspublish')
  3. const cloudfront = require('gulp-cloudfront-invalidate-aws-publish')
  4. const parallelize = require('concurrent-transform')
  5. // https://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html
  6. const config = {
  7. // Required
  8. params: {
  9. Bucket: process.env.AWS_BUCKET_NAME
  10. },
  11. credentials: {
  12. accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  13. secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  14. signatureVersion: 'v3'
  15. },
  16. // Optional
  17. deleteOldVersions: false, // NOT FOR PRODUCTION
  18. distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID
  19. region: process.env.AWS_DEFAULT_REGION,
  20. headers: {
  21. /* 'Cache-Control': 'max-age=315360000, no-transform, public', */
  22. },
  23. // Sensible Defaults - gitignore these Files and Dirs
  24. distDir: 'dist',
  25. indexRootPath: true,
  26. cacheFileName: '.awspublish',
  27. concurrentUploads: 10,
  28. wait: true // wait for CloudFront invalidation to complete (about 30-60 seconds)
  29. }
  30. gulp.task('deploy', function () {
  31. // create a new publisher using S3 options
  32. // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
  33. const publisher = awspublish.create(config)
  34. let g = gulp.src('./' + config.distDir + '/**')
  35. // publisher will add Content-Length, Content-Type and headers specified above
  36. // If not specified it will set x-amz-acl to public-read by default
  37. g = g.pipe(
  38. parallelize(publisher.publish(config.headers), config.concurrentUploads)
  39. )
  40. // Invalidate CDN
  41. if (config.distribution) {
  42. console.log('Configured with CloudFront distribution')
  43. g = g.pipe(cloudfront(config))
  44. } else {
  45. console.log(
  46. 'No CloudFront distribution configured - skipping CDN invalidation'
  47. )
  48. }
  49. // Delete removed files
  50. if (config.deleteOldVersions) {
  51. g = g.pipe(publisher.sync())
  52. }
  53. // create a cache file to speed up consecutive uploads
  54. g = g.pipe(publisher.cache())
  55. // print upload updates to console
  56. g = g.pipe(awspublish.reporter())
  57. return g
  58. })

4.5) Deploy and debug

Run it:

  1. ./deploy.sh

You should get an output similar to this:

  1. $ ./deploy.sh
  2. Found '/home/michael/scm/example.com/www/.nvmrc' with version <8>
  3. Now using node v8.11.2 (npm v5.6.0)
  4. > example.com@1.0.0 generate /home/michael/scm/example.com/www
  5. > nuxt generate
  6. nuxt:generate Generating... +0ms
  7. nuxt:build App root: /home/michael/scm/example.com/www +0ms
  8. nuxt:build Generating /home/michael/scm/example.com/www/.nuxt files... +0ms
  9. nuxt:build Generating files... +36ms
  10. nuxt:build Generating routes... +10ms
  11. nuxt:build Building files... +24ms
  12. ████████████████████ 100%
  13. Build completed in 7.009s
  14. DONE Compiled successfully in 7013ms 21:25:22
  15. Hash: 421d017116d2d95dd1e3
  16. Version: webpack 3.12.0
  17. Time: 7013ms
  18. Asset Size Chunks Chunk Names
  19. pages/index.ef923f795c1cecc9a444.js 10.6 kB 0 [emitted] pages/index
  20. layouts/default.87a49937c330bdd31953.js 2.69 kB 1 [emitted] layouts/default
  21. pages/our-values.f60c731d5c3081769fd9.js 3.03 kB 2 [emitted] pages/our-values
  22. pages/join-us.835077c4e6b55ed1bba4.js 1.3 kB 3 [emitted] pages/join-us
  23. pages/how.75f8cb5bc24e38bca3b3.js 2.59 kB 4 [emitted] pages/how
  24. app.6dbffe6ac4383bd30a92.js 202 kB 5 [emitted] app
  25. vendor.134043c361c9ad199c6d.js 6.31 kB 6 [emitted] vendor
  26. manifest.421d017116d2d95dd1e3.js 1.59 kB 7 [emitted] manifest
  27. + 3 hidden assets
  28. Hash: 9fd206f4b4e571e9571f
  29. Version: webpack 3.12.0
  30. Time: 2239ms
  31. Asset Size Chunks Chunk Names
  32. server-bundle.json 306 kB [emitted]
  33. nuxt: Call generate:distRemoved hooks (1) +0ms
  34. nuxt:generate Destination folder cleaned +10s
  35. nuxt: Call generate:distCopied hooks (1) +8ms
  36. nuxt:generate Static & build files copied +7ms
  37. nuxt:render Rendering url /our-values +0ms
  38. nuxt:render Rendering url /how +67ms
  39. nuxt:render Rendering url /join-us +1ms
  40. nuxt:render Rendering url / +0ms
  41. nuxt: Call generate:page hooks (1) +913ms
  42. nuxt: Call generate:page hooks (1) +205ms
  43. nuxt: Call generate:page hooks (1) +329ms
  44. nuxt: Call generate:page hooks (1) +361ms
  45. nuxt:generate Generate file: /our-values/index.html +2s
  46. nuxt:generate Generate file: /how/index.html +0ms
  47. nuxt:generate Generate file: /join-us/index.html +0ms
  48. nuxt:generate Generate file: /index.html +0ms
  49. nuxt:render Rendering url / +2s
  50. nuxt: Call generate:done hooks (1) +4ms
  51. nuxt:generate HTML Files generated in 11.8s +5ms
  52. nuxt:generate Generate done +0ms
  53. [21:25:27] Using gulpfile ~/scm/example.com/www/gulpfile.js
  54. [21:25:27] Starting 'deploy'...
  55. Configured with CloudFront distribution
  56. [21:25:27] [cache] README.md
  57. [21:25:27] [cache] android-chrome-192x192.png
  58. [21:25:27] [cache] android-chrome-512x512.png
  59. [21:25:27] [cache] apple-touch-icon.png
  60. [21:25:27] [cache] browserconfig.xml
  61. [21:25:27] [cache] favicon-16x16.png
  62. [21:25:27] [cache] favicon-32x32.png
  63. [21:25:27] [cache] favicon.ico
  64. [21:25:27] [cache] favicon.svg
  65. [21:25:27] [cache] logo-branches.svg
  66. [21:25:27] [cache] logo-small.svg
  67. [21:25:27] [cache] logo.svg
  68. [21:25:27] [cache] mstile-150x150.png
  69. [21:25:27] [cache] og-image.jpg
  70. [21:25:27] [cache] safari-pinned-tab.svg
  71. [21:25:27] [cache] site.webmanifest
  72. [21:25:28] [create] _nuxt/manifest.421d017116d2d95dd1e3.js
  73. [21:25:29] [update] 200.html
  74. [21:25:30] [create] videos/flag.jpg
  75. [21:25:30] [create] _nuxt/vendor.134043c361c9ad199c6d.js
  76. [21:25:34] [create] videos/flag.mp4
  77. [21:25:34] [cache] _nuxt/pages/how.75f8cb5bc24e38bca3b3.js
  78. [21:25:34] [cache] _nuxt/pages/join-us.835077c4e6b55ed1bba4.js
  79. [21:25:34] [cache] _nuxt/pages/our-values.f60c731d5c3081769fd9.js
  80. [21:25:36] [update] our-values/index.html
  81. [21:25:36] [create] _nuxt/layouts/default.87a49937c330bdd31953.js
  82. [21:25:36] [create] _nuxt/app.6dbffe6ac4383bd30a92.js
  83. [21:25:37] [create] _nuxt/pages/index.ef923f795c1cecc9a444.js
  84. [21:25:38] [update] join-us/index.html
  85. [21:25:38] [update] how/index.html
  86. [21:25:43] [create] videos/flag.webm
  87. [21:25:43] [update] index.html
  88. [21:25:43] CloudFront invalidation created: I16NXXXXX4JDOA
  89. [21:26:09] Finished 'deploy' after 42 s

Note that the CloudFront invalidation created: XXXX is the only output from the CloudFront invalidation npm package. If you don’t see that, it’s not working.