@risingstack/nx-seo
Version:
An express middleware for NX, used for SEO. Works by sending prerendered HTML pages to crawlers.
75 lines (68 loc) • 2.15 kB
JavaScript
const spiderDetector = require('spider-detector')
const jsdom = require('jsdom')
const MutationObserver = require('./mutationObserver')
module.exports = function seoFactory (config) {
return function seo (req, res, next) {
if (!spiderDetector.isSpider(req.get('user-agent')) || !req.accepts('html')) {
return next()
}
config = validateAndFillConfig(config)
const domConfig = {
url: req.protocol + '://' + req.get('host') + req.originalUrl,
created: onCreated,
onload: onLoad,
features: {
FetchExternalResources: ["script"],
ProcessExternalResources: ["script"],
SkipExternalResources: false
}
}
if (config.debug) {
domConfig.virtualConsole = jsdom.createVirtualConsole().sendTo(console)
}
jsdom.env(domConfig)
function onCreated (err, window) {
if (err) {
throw err
}
window.requestAnimationFrame = window.requestAnimationFrame || window.setTimeout
window.MutationObserver = window.MutationObserver || MutationObserver(window)
}
function onLoad (window) {
setTimeout(respondWithHTML, config.timeout, window)
}
function respondWithHTML (window) {
const scripts = window.document.getElementsByTagName('script')
for (const script of scripts) {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
}
res.set('Content-Type', 'text/html')
res.send(window.document.documentElement.outerHTML)
window.close()
}
}
}
function validateAndFillConfig (config) {
if (config === undefined) {
config = {}
}
if (typeof config !== 'object') {
throw new TypeError('config must be undefined or an object')
}
if (config.timeout === undefined) {
config.timeout = 0
}
if (typeof config.timeout !== 'number') {
throw new TypeError('config.timeout must be a number or undefined')
}
if (config.debug === undefined) {
config.debug = false
}
if (typeof config.debug !== 'boolean') {
throw new TypeError('config.debug must be a boolean or undefined')
}
return config
}