prerender-node = function (req, res, next) { if(!prerender.shouldShowPrerenderedPage(req)) return next(); prerender.beforeRenderFn(req, function(err, cachedRender) { if (!err && cachedRender) { if (typeof cachedRender == 'string') { res.writeHead(200, { "Content-Type": "text/html" }); return res.end(cachedRender); } else if (typeof cachedRender == 'object') { res.writeHead(cachedRender.status || 200, { "Content-Type": "text/html" }); return res.end(cachedRender.body || ''); } } prerender.getPrerenderedPageResponse(req, function(err, prerenderedResponse){ prerender.afterRenderFn(err, req, prerenderedResponse); if(prerenderedResponse){ res.writeHead(prerenderedResponse.statusCode, prerenderedResponse.headers); return res.end(prerenderedResponse.body); } else { next(err); } }); }); }
n/a
afterRenderFn = function (err, req, prerender_res) { if (!this.afterRender) return; this.afterRender(err, req, prerender_res); }
...
"Content-Type": "text/html"
});
return res.end(cachedRender.body || '');
}
}
prerender.getPrerenderedPageResponse(req, function(err, prerenderedResponse){
prerender.afterRenderFn(err, req, prerenderedResponse);
if(prerenderedResponse){
res.writeHead(prerenderedResponse.statusCode, prerenderedResponse.headers);
return res.end(prerenderedResponse.body);
} else {
next(err);
}
...
beforeRenderFn = function (req, done) { if (!this.beforeRender) return done(); return this.beforeRender(req, done); }
...
var request = require('request')
, url = require('url')
, zlib = require('zlib');
var prerender = module.exports = function(req, res, next) {
if(!prerender.shouldShowPrerenderedPage(req)) return next();
prerender.beforeRenderFn(req, function(err, cachedRender) {
if (!err && cachedRender) {
if (typeof cachedRender == 'string') {
res.writeHead(200, {
"Content-Type": "text/html"
});
return res.end(cachedRender);
...
blacklisted = function (blacklist) { prerender.blacklist = typeof blacklist === 'string' ? [blacklist] : blacklist; return this; }
...
app.use(require('prerender-node').whitelisted(['/search', '/users/.*/profile']));
```
### Blacklist
Blacklist a single url path or multiple url paths. Compares using regex, so be specific when possible. If a blacklist is supplied
, all url's will be prerendered except ones containing a blacklist path.
```js
app.use(require('prerender-node').blacklisted('^/search'));
```
```js
app.use(require('prerender-node').blacklisted(['/search', '/users/.*/profile']));
```
### beforeRender
...
buildApiUrl = function (req) { var prerenderUrl = prerender.getPrerenderServiceUrl(); var forwardSlash = prerenderUrl.indexOf('/', prerenderUrl.length - 1) !== -1 ? '' : '/'; var protocol = req.connection.encrypted ? "https" : "http"; if (req.headers['cf-visitor']) { var match = req.headers['cf-visitor'].match(/"scheme":"(http|https)"/); if (match) protocol = match[1]; } if (req.headers['x-forwarded-proto']) { protocol = req.headers['x-forwarded-proto'].split(',')[0]; } if (this.protocol) { protocol = this.protocol; } var fullUrl = protocol + "://" + (this.host || req.headers['x-forwarded-host'] || req.headers['host']) + req.url; return prerenderUrl + forwardSlash + fullUrl; }
...
};
prerender.prerenderServerRequestOptions = {};
prerender.getPrerenderedPageResponse = function(req, callback) {
var options = {
uri: url.parse(prerender.buildApiUrl(req)),
followRedirect: false,
headers: {}
};
for (var attrname in this.prerenderServerRequestOptions) { options[attrname] = this.prerenderServerRequestOptions[attrname]; }
if (this.forwardHeaders === true) {
Object.keys(req.headers).forEach(function(h) {
// Forwarding the host header can cause issues with server platforms that require it to match the URL
...
getPrerenderServiceUrl = function () { return this.prerenderServiceUrl || process.env.PRERENDER_SERVICE_URL || 'http://service.prerender.io/'; }
...
response.body = content;
callback(null, response);
});
};
prerender.buildApiUrl = function(req) {
var prerenderUrl = prerender.getPrerenderServiceUrl();
var forwardSlash = prerenderUrl.indexOf('/', prerenderUrl.length - 1) !== -1 ? '' : '/';
var protocol = req.connection.encrypted ? "https" : "http";
if (req.headers['cf-visitor']) {
var match = req.headers['cf-visitor'].match(/"scheme":"(http|https)"/);
if (match) protocol = match[1];
}
...
getPrerenderedPageResponse = function (req, callback) { var options = { uri: url.parse(prerender.buildApiUrl(req)), followRedirect: false, headers: {} }; for (var attrname in this.prerenderServerRequestOptions) { options[attrname] = this.prerenderServerRequestOptions[attrname]; } if (this.forwardHeaders === true) { Object.keys(req.headers).forEach(function(h) { // Forwarding the host header can cause issues with server platforms that require it to match the URL if (h == 'host') { return; } options.headers[h] = req.headers[h]; }); } options.headers['User-Agent'] = req.headers['user-agent']; options.headers['Accept-Encoding'] = 'gzip'; if(this.prerenderToken || process.env.PRERENDER_TOKEN) { options.headers['X-Prerender-Token'] = this.prerenderToken || process.env.PRERENDER_TOKEN; } request.get(options).on('response', function(response) { if(response.headers['content-encoding'] && response.headers['content-encoding'] === 'gzip') { prerender.gunzipResponse(response, callback); } else { prerender.plainResponse(response, callback); } }).on('error', function(err) { callback(err); }); }
...
res.writeHead(cachedRender.status || 200, {
"Content-Type": "text/html"
});
return res.end(cachedRender.body || '');
}
}
prerender.getPrerenderedPageResponse(req, function(err, prerenderedResponse){
prerender.afterRenderFn(err, req, prerenderedResponse);
if(prerenderedResponse){
res.writeHead(prerenderedResponse.statusCode, prerenderedResponse.headers);
return res.end(prerenderedResponse.body);
} else {
next(err);
...
gunzipResponse = function (response, callback) { var gunzip = zlib.createGunzip() , content = ''; gunzip.on('data', function(chunk) { content += chunk; }); gunzip.on('end', function() { response.body = content; delete response.headers['content-encoding']; delete response.headers['content-length']; callback(null, response); }); gunzip.on('error', function(err){ callback(err); }); response.pipe(gunzip); }
...
options.headers['Accept-Encoding'] = 'gzip';
if(this.prerenderToken || process.env.PRERENDER_TOKEN) {
options.headers['X-Prerender-Token'] = this.prerenderToken || process.env.PRERENDER_TOKEN;
}
request.get(options).on('response', function(response) {
if(response.headers['content-encoding'] && response.headers['content-encoding'] === 'gzip
') {
prerender.gunzipResponse(response, callback);
} else {
prerender.plainResponse(response, callback);
}
}).on('error', function(err) {
callback(err);
});
};
...
plainResponse = function (response, callback) { var content = ''; response.on('data', function(chunk) { content += chunk; }); response.on('end', function() { response.body = content; callback(null, response); }); }
...
options.headers['X-Prerender-Token'] = this.prerenderToken || process.env.PRERENDER_TOKEN;
}
request.get(options).on('response', function(response) {
if(response.headers['content-encoding'] && response.headers['content-encoding'] === 'gzip
') {
prerender.gunzipResponse(response, callback);
} else {
prerender.plainResponse(response, callback);
}
}).on('error', function(err) {
callback(err);
});
};
prerender.gunzipResponse = function(response, callback) {
...
set = function (name, value) { this[name] = value; return this; }
...
```js
app.use(require('prerender-node'));
```
or if you have an account on [prerender.io](http://prerender.io) and want to use your token:
```js
app.use(require('prerender-node').set('prerenderToken', 'YOUR_TOKEN
'));
```
`Note` If you're testing locally, you'll need to run the [prerender server](https://github.com/prerender/prerender) locally
so that it has access to your server.
This middleware is tested with Express3 and Express4, but has no explicit dependency on either.
## Testing
...
shouldShowPrerenderedPage = function (req) { var userAgent = req.headers['user-agent'] , bufferAgent = req.headers['x-bufferbot'] , isRequestingPrerenderedPage = false; if(!userAgent) return false; if(req.method != 'GET' && req.method != 'HEAD') return false; //if it contains _escaped_fragment_, show prerendered page var parsedQuery = url.parse(req.url, true).query; if(parsedQuery && parsedQuery['_escaped_fragment_'] !== undefined) isRequestingPrerenderedPage = true; //if it is a bot...show prerendered page if(prerender.crawlerUserAgents.some(function(crawlerUserAgent){ return userAgent.toLowerCase().indexOf(crawlerUserAgent.toLowerCase ()) !== -1;})) isRequestingPrerenderedPage = true; //if it is BufferBot...show prerendered page if(bufferAgent) isRequestingPrerenderedPage = true; //if it is a bot and is requesting a resource...dont prerender if(prerender.extensionsToIgnore.some(function(extension){return req.url.toLowerCase().indexOf(extension) !== -1;})) return false ; //if it is a bot and not requesting a resource and is not whitelisted...dont prerender if(Array.isArray(this.whitelist) && this.whitelist.every(function(whitelisted){return (new RegExp(whitelisted)).test(req.url) === false;})) return false; //if it is a bot and not requesting a resource and is not blacklisted(url or referer)...dont prerender if(Array.isArray(this.blacklist) && this.blacklist.some(function(blacklisted){ var blacklistedUrl = false , blacklistedReferer = false , regex = new RegExp(blacklisted); blacklistedUrl = regex.test(req.url) === true; if(req.headers['referer']) blacklistedReferer = regex.test(req.headers['referer']) === true; return blacklistedUrl || blacklistedReferer; })) return false; return isRequestingPrerenderedPage; }
...
var request = require('request')
, url = require('url')
, zlib = require('zlib');
var prerender = module.exports = function(req, res, next) {
if(!prerender.shouldShowPrerenderedPage(req)) return next();
prerender.beforeRenderFn(req, function(err, cachedRender) {
if (!err && cachedRender) {
if (typeof cachedRender == 'string') {
res.writeHead(200, {
"Content-Type": "text/html"
...
whitelisted = function (whitelist) { prerender.whitelist = typeof whitelist === 'string' ? [whitelist] : whitelist; return this; }
...
# Customization
### Whitelist
Whitelist a single url path or multiple url paths. Compares using regex, so be specific when possible. If a whitelist is supplied
, only urls containing a whitelist path will be prerendered.
```js
app.use(require('prerender-node').whitelisted('^/search'));
```
```js
app.use(require('prerender-node').whitelisted(['/search', '/users/.*/profile']));
```
### Blacklist
...