Static Websites

on AWS

w/ Nuxt

Michael Cole
 

Freelancer

Poverty Abolitionist

 

michael@CoalitionToAbolishPoverty.org

Static Website on AWS w/ Nuxt

What is the difference between 'nuxt build'
and 'nuxt generate' ?

What is a web app?

  • Browse to URL
  • Login
  • Do stuff
  • Come back later
  • Remembers yer stuff

What is a static site?

  • Browse to URL
  • Click around
  • Read stuff
     
  • Written using declarative content (markdown, not html)

Web Tech Tree

GET URL:  server process

GET URL:  file

Dynamic Page (PHP, Nuxt Universal)

Static Page (jQuery)

Static Page

<html>
  <body>
    stuff
  </body>
</html>

 

Serve content from files.

 

 

Apache, Nginx

Dynamic Page

<html>
  <body>
    <?php echo "stuff"; ?>
  </body>
</html>

 

Serve content from URL, templates, and cookies.

 

CGI, PHP, Ruby on Rails, WordPress

Web Tech Tree

Dynamic Page (PHP, Nuxt Universal)

Single Page App (Nuxt SPA)

Static Site Gen (Github pages)

Static Page (jQuery)

GET URL: server process

Click 'page': browser

GET URL: file

Click 'page': file

SPA

<html>
  <body>
     <a ui-sref="stuff">x</a>
  </body>
</html>

 

One HTML for all URLS.  Browser routes "pages".

 

Angular + ui-router, Backbone, Ember, Vue

Static Site

index.html
blog/hello.html
blog/post-more-often.html
about-us/index.html
contact/index.html

 

Prerender HTML for each URL w/ declarative content.

 

Jekyll, Wintersmith, Hexo, Github pages, Nuxt

 

Web Tech Tree

Dynamic Page (PHP, Nuxt Universal)

Single Page App (Nuxt SPA)

Static Site Gen (Github pages)

Static Page (jQuery)

GET URL: server process

Click 'page': browser

GET URL: file

Click 'page': file

!!!

Web Tech Tree

Dynamic Page (PHP, Nuxt Universal)

Single Page App (Nuxt SPA)

Static Site Gen (Github pages)

Static Page (jQuery)

GET URL: server process

Click 'page': browser

 

! Not SEO friendly !

GET URL: file

Click 'page': file

 

! Not an App !

!!!

nuxt static site (pre-rendered)

 

By default, Vue components produce and manipulate DOM in the browser as output.

 

However, it is also possible to:

  1. render the same components into HTML strings on the server (e.g. as files)
  2. send them directly to the browser
  3. finally "hydrate" the static markup into a fully interactive app on the client  (asyncData)

 

This is a Vue.js feature:

https://ssr.vuejs.org/#what-is-server-side-rendering-ssr

Web Tech Tree

Statically Generated SPA (Nuxt Static Site)

Progressive Web App (Offline, Nuxt PWA)

GET URL: file

Click page: browser

"Hydrate": REST (asyncData)

Dynamic Page (PHP, Nuxt Universal)

Single Page App (Nuxt SPA)

Static Site Gen (Github pages)

Static Page (jQuery)

nuxt generate

  • Transpile declarative content
  • into a Nuxt Universal/Vue SPA

 

Then:

  • pre-render with Vue SSR
  • into a Nuxt Static Site

nuxt build

  • Transpile declarative content
  • into a Nuxt Universal/Vue SPA

code & content

 

/layouts

/pages

/assets

/components

/plugins

Nuxt Universal App (Express)

 

/.nuxt/index.js

Static Site

 

/index.html

/pages/index.html

/_nuxt/*

nuxt build

(aka Server Rendered Deployment) (aka Nuxt Universal)

// ------------------------- Dev:

nuxt

// or

npm run dev



// ------------------------- Production:

// compile to .nuxt directory

nuxt build

// run web server with Node.js

nuxt start  

nuxt build -> .nuxt

 

GET page: server process

Click page: browser

// ------------------------- Dev:

nuxt || npm run dev



// ------------------------- Production:

// compile static site to dist directory

nuxt generate

// Push files to web server...

nuxt_deploy_or_whatever.sh

nuxt generate

(aka Server Generated Deployment) (pre-rendered) (static site)

nuxt generate -> dist

GET page: file

Click page: browser

"Hydrate": REST (asyncData)

Static Websites

on AWS

w/ Nuxt

nuxt deploy?  Nope.

Laptop:

`nuxt generate`

?

Cloud:

?

nuxt deploy?  Nope.

Laptop:

`nuxt generate`

Why Cloud?

  • More secure.  No server to p0wn.
  • Faster page loads.  CDN
  • Free SSL cert.
  • Cheaper hosting.  $0.84 /month

 

Why AWS? 

I already have an account.

Gulp code from wintersmith project

nuxt on AWS w/ S3 + Cloudfront

SSL

CDN

Browser

S3 bucket

CloudFront

npm run deploy

nuxt generate

deploy.sh

gulp

AWS

Laptop

AWS: S3 + Cloudfront

SSL

CDN

Browser

S3 bucket

CloudFront

Stores files

Static website hosting

http://coalitiontoabolishpoverty.org.s3-website-us-east-1.amazonaws.com

 

Content Delivery Network

Certificate Manager

"Invalidate" cache to refresh from S3.

https://coalitiontoabolishpoverty.org

nuxt on AWS w/ S3 + Cloudfront

SSL

CDN

Browser

S3 bucket

CloudFront

Setup AWS stuff:

1. S3 bucket

2. CloudFront -> S3

3. DNS -> CloudFront

4. Securely push files -> S3

 

Deploy our stuff:

1. Push files -> S3

2. Invalidate CloudFront to refresh from S3

nuxt on AWS w/ S3 + Cloudfront

Setup AWS stuff:

1. S3 bucket

2. CloudFront -> S3

3. DNS -> CloudFront

4. Securely push files -> S3

SSL

CDN

Browser

S3 bucket

CloudFront

Read the Nuxt FAQ

nuxt on AWS w/ S3 + Cloudfront

AWS Console tour

  • S3 Bucket
    • Static Site (AWS_BUCKET_NAME)
    • Bucket Policy (Public)
  • IAM User Policy
    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY
  • CloudFront
    • AWS_CLOUDFRONT (uppercase)
    • xxxx.cloudfront.net
  • DNS ALIAS or CNAME

SSL

CDN

Browser

S3 bucket

CloudFront

nuxt on AWS w/ S3 + Cloudfront

Setup AWS stuff:

1. S3 bucket

2. CloudFront -> S3

3. DNS -> CloudFront

4. Securely push files -> S3

 

We need this data:

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

 

Read the Nuxt FAQ

SSL

CDN

Browser

S3 bucket

CloudFront

nuxt on AWS w/ S3 + Cloudfront

Deploy our stuff:

1. Push files -> S3

2. Invalidate CloudFront to refresh cache

    (see new content right away)

 

gulp - streaming build system w/o a webpack PhD

 

Problem solved w/ NPM packages:

  • gulp
  • gulp-awspublish
  • gulp-cloudfront-invalidate-aws-publish
  • concurrent-transform (parallel uploads)

SSL

CDN

Browser

S3 bucket

CloudFront

Deploy our stuff:

1. deploy.sh

2. .gitignore

3. packages!

4. gulpfile.js

5. Deploy already!

#!/bin/bash

export AWS_ACCESS_KEY_ID="key" 
export AWS_SECRET_ACCESS_KEY="secret" 
export AWS_BUCKET_NAME="example.com" 
export AWS_CLOUDFRONT="UPPERCASE"

# If nvm (node version manager),
[ -s "$HOME/.nvm/nvm.sh" ] \
   && source "$HOME/.nvm/nvm.sh" \
   && nvm use

# Npm install if not already.
[ ! -d "node_modules" ] && npm install

nuxt generate
gulp deploy

# or configure package.json scripts
npm run generate
npm run deploy

Deploy our stuff:

1. deploy.sh

2. .gitignore

3. packages!

4. gulpfile.js

5. Deploy already!

$ chmod +x deploy.sh

$ echo "
# Don't commit build files
node_modules
dist
.nuxt
.awspublish
deploy.sh
" >> .gitignore

Deploy our stuff:

1. deploy.sh

2. .gitignore

3. packages!

4. gulpfile.js

5. Deploy already!

$ npm install --save-dev \
 gulp \
 gulp-awspublish \
 gulp-cloudfront-invalidate-aws-publish \
 concurrent-transform

$ npm install -g gulp

4. gulpfile.js

var gulp = require('gulp');
var awspublish = require('gulp-awspublish');
var cloudfront = require('gulp-cloudfront-invalidate-aws-publish');
var parallelize = require('concurrent-transform');

// https://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html

var config = {

  // Required
  params: { Bucket: process.env.AWS_BUCKET_NAME },
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,

  // Optional
  deleteOldVersions: false,                 // NOT FOR PRODUCTION
  distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID
  region: process.env.AWS_DEFAULT_REGION,
  headers: { /*'Cache-Control': 'max-age=315360000, no-transform, public',*/ },

  // Sensible Defaults - gitignore these Files and Dirs
  distDir: 'dist',
  indexRootPath: true,
  cacheFileName: '.awspublish',
  concurrentUploads: 10,
  wait: true,  // wait for CloudFront invalidation to complete (about 30-60 seconds)
}

gulp.task('deploy', function() {
  // create a new publisher using S3 options
  // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
  var publisher = awspublish.create(config, config);

  // Collect all the files in distDir
  var g = gulp.src('./' + config.distDir + '/**');

  // Send those files through publish.
  g = g.pipe(parallelize(publisher.publish(config.headers), config.concurrentUploads))

  // Invalidate distribution cache so new content appears
  if (config.distribution) {
    console.log('Configured with CloudFront distribution');
    g = g.pipe(cloudfront(config));
  } else {
    console.log('No CloudFront distribution configured - skipping CDN invalidation');
  }

  // Delete removed files - breaks older versions being viewed
  if (config.deleteOldVersions) g = g.pipe(publisher.sync());

  // create a local cache of files to speed up consecutive uploads
  g = g.pipe(publisher.cache());

  // print upload updates to console
  g = g.pipe(awspublish.reporter());

  return g;
});
$ ./deploy.sh                                                                                                                 

Found '/home/michael/scm/example.com/www/.nvmrc' with version <8>
Now using node v8.11.2 (npm v5.6.0)

> example.com@1.0.0 generate /home/michael/scm/example.com/www
> nuxt generate

  nuxt:generate Generating... +0ms
  nuxt:build App root: /home/michael/scm/example.com/www +0ms
  nuxt:build Generating /home/michael/scm/example.com/www/.nuxt files... +0ms
  nuxt:build Generating files... +36ms
  nuxt:build Generating routes... +10ms
  nuxt:build Building files... +24ms
  ████████████████████ 100% 

Build completed in 7.009s



 DONE  Compiled successfully in 7013ms                                                                                                                                     21:25:22

Hash: 421d017116d2d95dd1e3
Version: webpack 3.12.0
Time: 7013ms
                                   Asset     Size  Chunks             Chunk Names
     pages/index.ef923f795c1cecc9a444.js  10.6 kB       0  [emitted]  pages/index
 layouts/default.87a49937c330bdd31953.js  2.69 kB       1  [emitted]  layouts/default
pages/our-values.f60c731d5c3081769fd9.js  3.03 kB       2  [emitted]  pages/our-values
   pages/join-us.835077c4e6b55ed1bba4.js   1.3 kB       3  [emitted]  pages/join-us
       pages/how.75f8cb5bc24e38bca3b3.js  2.59 kB       4  [emitted]  pages/how
             app.6dbffe6ac4383bd30a92.js   202 kB       5  [emitted]  app
          vendor.134043c361c9ad199c6d.js  6.31 kB       6  [emitted]  vendor
        manifest.421d017116d2d95dd1e3.js  1.59 kB       7  [emitted]  manifest
 + 3 hidden assets
Hash: 9fd206f4b4e571e9571f
Version: webpack 3.12.0
Time: 2239ms
             Asset    Size  Chunks             Chunk Names
server-bundle.json  306 kB          [emitted]  
  nuxt: Call generate:distRemoved hooks (1) +0ms
  nuxt:generate Destination folder cleaned +10s
  nuxt: Call generate:distCopied hooks (1) +8ms
  nuxt:generate Static & build files copied +7ms
  nuxt:render Rendering url /our-values +0ms
  nuxt:render Rendering url /how +67ms
  nuxt:render Rendering url /join-us +1ms
  nuxt:render Rendering url / +0ms
  nuxt: Call generate:page hooks (1) +913ms
  nuxt: Call generate:page hooks (1) +205ms
  nuxt: Call generate:page hooks (1) +329ms
  nuxt: Call generate:page hooks (1) +361ms
  nuxt:generate Generate file: /our-values/index.html +2s
  nuxt:generate Generate file: /how/index.html +0ms
  nuxt:generate Generate file: /join-us/index.html +0ms
  nuxt:generate Generate file: /index.html +0ms
  nuxt:render Rendering url / +2s
  nuxt: Call generate:done hooks (1) +4ms
  nuxt:generate HTML Files generated in 11.8s +5ms
  nuxt:generate Generate done +0ms
[21:25:27] Using gulpfile ~/scm/example.com/www/gulpfile.js
[21:25:27] Starting 'deploy'...
Configured with CloudFront distribution
[21:25:27] [cache]  README.md
[21:25:27] [cache]  android-chrome-192x192.png
[21:25:27] [cache]  android-chrome-512x512.png
[21:25:27] [cache]  apple-touch-icon.png
[21:25:27] [cache]  browserconfig.xml
[21:25:27] [cache]  favicon-16x16.png
[21:25:27] [cache]  favicon-32x32.png
[21:25:27] [cache]  favicon.ico
[21:25:27] [cache]  favicon.svg
[21:25:27] [cache]  logo-branches.svg
[21:25:27] [cache]  logo-small.svg
[21:25:27] [cache]  logo.svg
[21:25:27] [cache]  mstile-150x150.png
[21:25:27] [cache]  og-image.jpg
[21:25:27] [cache]  safari-pinned-tab.svg
[21:25:27] [cache]  site.webmanifest
[21:25:28] [create] _nuxt/manifest.421d017116d2d95dd1e3.js
[21:25:29] [update] 200.html
[21:25:30] [create] videos/flag.jpg
[21:25:30] [create] _nuxt/vendor.134043c361c9ad199c6d.js
[21:25:34] [create] videos/flag.mp4
[21:25:34] [cache]  _nuxt/pages/how.75f8cb5bc24e38bca3b3.js
[21:25:34] [cache]  _nuxt/pages/join-us.835077c4e6b55ed1bba4.js
[21:25:34] [cache]  _nuxt/pages/our-values.f60c731d5c3081769fd9.js
[21:25:36] [update] our-values/index.html
[21:25:36] [create] _nuxt/layouts/default.87a49937c330bdd31953.js
[21:25:36] [create] _nuxt/app.6dbffe6ac4383bd30a92.js
[21:25:37] [create] _nuxt/pages/index.ef923f795c1cecc9a444.js
[21:25:38] [update] join-us/index.html
[21:25:38] [update] how/index.html
[21:25:43] [create] videos/flag.webm
[21:25:43] [update] index.html
[21:25:43] CloudFront invalidation created: I16NXXXXX4JDOA
[21:26:09] Finished 'deploy' after 42 s

5. Deploy Already!

michael@CoalitionToAbolishPoverty.org

Made with Slides.com