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
}));
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
};
};
{{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 withthe 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)
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
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,205