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
...