Brad Oyler

Lead Developer,  NBCNews.com

@bradoyler
~

Tweet questions with #BuxCoTech?

Building content sites


@

content


Traffic - Last 30 days


"BIG" redesign (feb '14)


previous cover


the response



Nasty Tweets

Negative Facebook Comments

Drop in unique visitors


preview.nbcnews.com (may)


the stack


Express.js
+
Handlebars.js
+
Route-Cache
+
Redis

why?


web + worker

Worker:
"dependencies": {
    "redis": "0.12.0"
    "async": "0.9.0",
    "request": "2.34.0"
}

Web:
"dependencies": {
    "redis": "0.12.0",
    "route-cache": "0.2.0",
    "express": "4.0.0",
    "express-handlebars" : "1.1.0"
},

"devDependencies": {
   "node-sass": "0.16.0",
   "grunt": "~0.4.1"
}

route-cache

module.exports.cacheSeconds = function(ttl) {

  return function(req, res, next) {
    var cache = cachestore.get(req.originalUrl);
    if (cache) {
      res.send(cache);
    } else {
      var send = res.send;
      res.send = function(string) {
        var body = string instanceof Buffer ? string.toString() : string;
        cachestore.put(req.originalUrl, body, ttl);
        send.call(this, body);
      };
      next();
    }
  }
};
 npm install route-cache --save

index route

var dataService = require('../dataService');
var routeCache  = require('route-cache');

function controller(req, res, next) {
   dataService.getKeys(['metadata','coverstories'], function(model) {
     res.render('index', model);
   });

}

var router = express.Router();
router.get('/', routeCache.cacheSeconds(15), controller);

module.exports = router;

data-service

var redis = require('redis');
var async = require('async');

function getKey(key, callback) {

       redis.get(key, function(error, value) {
         callback(null, value);
       }
}

exports.getKeys = function(keys, callback) {

   async.map(keys, this.getKey, callback);

};

view Engine

app.engine('.hbs', expressHbs({
    defaultLayout: 'default',
    extname: '.hbs',
    layoutsDir: __dirname + '/views/layouts',
    partialsDir: [__dirname + '/views/partials'],
    helpers: hbsHelpers
}));

Q: Why handlebars? 

A: Familiar syntax, partials & helpers

HBS helpers

 module.exports = function() {
  return {
    ifindexby: this.ifindexby,
    formatdate: this.formatdate,
    decodeuri: this.decodeuri,
    link: this.linkHelper,
    'if-equal': this.ifEqual,
    'each-slice': this.eachSlice,
    'if-contains': this.ifContains,
    articleurl: this.articleurl,
    routeurl: this.routeurl,
    coverheadline: this.coverheadline
  };
};
this is where the magic happens, as it makes building out your app with templates & partials... dead simple

{{if-equal}}

exports.ifEqual = function(val, test, options) {
  var arrTest = test.split('||');
  for (var i = 0; i < arrTest.length; i++) {
    if (val === arrTest[i]) {
      return options.fn(this);
    }
  }
  return options.inverse(this);
};

{{#if-equal type 'photo'}}
  {{> article_photo}}
{{/if-equal}}

{{#if-equal type 'post||externallink'}}
  {{> article_post}}
{{/if-equal}}

true, these aren't logic-less templates, but so easy to work with

the client

Bower packages
"dependencies": {
  // css
    "bootstrap-sass-official": "~3.1.0",
    "typesettings": "~2.0.0",
    
  // javascript    
    "modernizr": "~2.6.2",
    "jquery": "~1.11.0",
    "jquery.lazyload": "~1.9.3",
    "jquery-waypoints": "~2.0.5",
    "jquery.cookie": "~1.4.1",
    "underscore": "1.5.2",
    "mustache": "~0.8.1",
    "backbone": "~1.1.2",
    "velocity": "~1.1.0"
}

and now...


(science)

benchmarking R/S

ApacheBench test
ab -n 200 -c 4 http://localhost:9001/

// 1. express(‘view cache’, false) & !routecache
Requests per second:    12.88 [#/sec] (mean)

// 2. express(‘view cache’, true) & !routecache
Requests per second:    21.38 [#/sec] (mean)

// 3. express(‘view cache’, true) & routecache(15)
Requests per second:    530.56 [#/sec] (mean)
github.com/bradoyler/express-content-demo

web performance


 

wpt filmstrip view


test results

Why does this really matter?


SEO
User Experience
SEO
Mobile  3G
SEO
Google likes us now




results

search traffic

unique visitors


final word

Testing/CI tools
Mocha, Runscope, Jenkins, SauceLabs

Deployment
grunt & usemin
AWS S3
gulp (soon)

thanks ;)



Content sites with NodeJs (rev2)

By bradoyler

Content sites with NodeJs (rev2)

One way to build content sites using Node.js, Express and server-side Handlebars templates

  • 2,093