🔌⚡ Nuxt module to prune html before sending it to the browser (it removes elements matching CSS selector(s)), useful for boosting performance showing a different HTML for bots/audits by removing all the scripts with dynamic rendering
npm install @luxdamore/nuxt-prune-html[![Code Quality][quality-src]][quality-href]
[![Downloads][npm-downloads-src]][npm-downloads-href]
[![Dependencies][dependencies-src]][dependencies-href]
[![Circle CI][circle-ci-src]][circle-ci-href]
[![Version][npm-version-src]][npm-version-href]
[![Donate][paypal-donate-src]][paypal-donate-href]
[quality-src]: https://img.shields.io/badge/code%20quality-A-informational?style=flat
[quality-href]: https://luxdamore.github.io/nuxt-prune-html/
[npm-downloads-src]: https://img.shields.io/npm/dt/@luxdamore/nuxt-prune-html.svg?style=flat&color=darkgreen
[npm-downloads-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html
[dependencies-src]: https://img.shields.io/badge/dependencies-up%20to%20date-darkgreen.svg?style=flat
[dependencies-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html
[circle-ci-src]: https://img.shields.io/circleci/project/github/LuXDAmore/nuxt-prune-html.svg?style=flat&color=darkgreen
[circle-ci-href]: https://circleci.com/gh/LuXDAmore/nuxt-prune-html
[npm-version-src]: https://img.shields.io/npm/v/@luxdamore/nuxt-prune-html/latest.svg?style=flat&color=darkorange&label=version
[npm-version-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html
[paypal-donate-src]: https://img.shields.io/badge/paypal-donate-black.svg?style=flat
[paypal-donate-href]: https://www.paypal.me/luxdamore
[patreon-donate-href]: https://www.patreon.com/luxdamore
[kofi-donate-href]: https://ko-fi.com/luxdamore
> Nuxt module to prune html before sending it to the browser (it removes elements matching CSS selector(s)), useful for boosting performance showing a different HTML for bots/audits by removing all the scripts with dynamic rendering.
Due to the versatility of Nuxt (and of the SSR in general), a website generated (or served) via node server, has everything it needs already injected in the HTML (ex. css styles). So, usually, for a bot, a audit or for a human, the website its almost visually the same with or without Javascript.
This library is born to remove all the scripts injected into the HTML only if a visitor is a Bot or a Performance Audit (ex. a Lighthouse Audit).
This should speed up (blazing fast) your nuxt-website up to a value of ~99 in performance because it cheats various scenarios.
Usually, with less assets, resources and html to download, the number of urls crawled by a bot are widely boosted 📈.
> Inspired by this rcfs and this issue.
- Prune based on default detection;
- match the user-agent;
- match a bot;
- match an audit;
- match a custom-header;
- Prune based on headers values (useful in/for Lambdas);
- Prune based on query parameters (useful during navigation, hybrid-experience).
> This could cause some unexpected behaviors, but..
Cons.:
- No SPA routing on client-side for bots and audits;
- No hydration on client-side for bots and audits:
- ex. vue-lazy-hydration need Javascript client-side code to trigger _hydrateOnInteraction_, _hydrateWhenIdle_ or _hydrateWhenVisible_;
- No components;
- Can break lazy-load for images.
Pros.:
- Some of these features aren't "used by" a bot/audit, so you don't really need them:
- bots doesn't handle SPA routing;
- components could lead in a slower TTI;
- components can contain a static placeholder;
- Images with lazy-load can be fixed with a native attribute, with a custom script or with classesSelectorsToKeep (_check the configuration_);
- Hydration decrease performance, so it's ok to prune it for bots or audits;
- Less HTML, assets and resources are served to browsers and clients;
- Bot/audit only have the Javascript they need;
- With less assets to download, the number of urls crawled are widely boosted;
- Bots, PageSpeed Insights, Google Measure and Lighthouse Audit are already pruned by the plugin with the default configuration;
- Faster web-vitals, faster TTI, faster FCP, faster FMP, faster all.
N.B.: _This is known as Dynamic Rendering and it's not considered black-hat or cloaking_.
___
#### 💡 Lighthouse
!Lighthouse Audit before
!Lighthouse Audit after
___
1. Install @luxdamore/nuxt-prune-html as a dependency:
- yarn add @luxdamore/nuxt-prune-html;
- or, npm install --save @luxdamore/nuxt-prune-html;
2. Append @luxdamore/nuxt-prune-html to the modules array of your nuxt.config.js.
``js
// nuxt.config.js
export default {
// Module - installation
modules: [ '@luxdamore/nuxt-prune-html' ],
// Module - default config
pruneHtml: {
enabled: false, // true in productionfalse
hideGenericMessagesInConsole: false, // in productionconsole.error
hideErrorsInConsole: false, // deactivate the methodhook:render:route
hookRenderRoute: true, // activate hook:generate:page
hookGeneratePage: true, // activate classesSelectorsToKeep
selectors: [
// CSS selectors to prune
'link[rel="preload"][as="script"]',
'script:not([type="application/ld+json"])',
],
classesSelectorsToKeep: [], // disallow pruning of scripts with this classes, n.b.: each is appended to every selectors, ex.: link[rel="preload"][as="script"]:not(__classesSelectorsToKeep__)cheerio.load(__config__)
link: [], // inject custom links, only if pruned
script: [], // inject custom scripts, only if pruned
htmlElementClass: null, // a string added as a class to the element if pruned
cheerio: {
// the config passed to the method
xmlMode: false,
},
types: [
// it's possibile to add different rules for pruning
'default-detect',
],
// 👇🏻 Type: default-detectheader-key
headerNameForDefaultDetection: 'user-agent', // The base for MobileDetection, usage request.headers[ headerNameForDefaultDetection ]request.header[ headerNameForDefaultDetection ]
auditUserAgent: 'lighthouse', // prune if match, could be a string or an array of stringsauditUserAgent
isAudit: true, // remove selectors if match with request.header[ headerNameForDefaultDetection ]
isBot: true, // remove selectors if is a bot
ignoreBotOrAudit: false, // remove selectors in any case, not depending on Bot or Audit
matchUserAgent: null, // prune if match, could be a string or an array of strings
// 👇🏻 Type: 'query-parameters'
queryParametersToPrune: [
// array of objects (key-value)
// trigger the pruning if 'query-parameters' is present in types and at least one value is matched, ex. /?prune=truequeryParametersToPrune
{
key: 'prune',
value: 'true',
},
],
queryParametersToExcludePrune: [], // same as , exclude the pruning if 'query-parameters' is present in types and at least one value is matched, this priority is over than queryParametersToPrune
// 👇🏻 Type: 'headers-exist'
headersToPrune: [], // same as queryParametersToPrune, but it checks request.headersqueryParamToExcludePrune
headersToExcludePrune: [], // same as , but it checks request.headers, this priority is over than headersToPrune
// Emitted events for callbacks methods
onBeforePrune: null, // ({ result, [ req, res ] }) => {}, req and res are not available on nuxt generatereq
onAfterPrune: null, // ({ result, [ req, res ] }) => {}, and res are not available on nuxt generate
},
};
`
With link and script it's possibile to add one or more objects on the pruned HTML, ex.:
`javascript
export default {
pruneHtml: {
link: [
{
rel: 'preload',
as: 'script',
href: '/my-custom-lazy-load-for-bots.js',
position: 'phead', // Default value is 'body', other allowed values are: 'phead', 'head' and 'pbody'
},
{
rel: 'stylesheet',
href: '/my-custom-styles-for-bots.css',
position: 'head',
},
],
script: [
{
src: '/my-custom-lazy-load-for-bots.js',
lazy: true,
defer: true,
},
],
},
};
`
> N.B.: _the config is only shallow merged, not deep merged_.
Possible values are [ 'default-detect', 'query-parameters', 'headers-exist' ]:
- default-detect: prune based on one header(request.headers[ headerNameForDefaultDetection ])isBot
- different checks with MobileDetect:
- , trigger .is( 'bot' ) method;auditUserAgent
- or matchUserAgent, trigger .match() method;query-parameters
- : prune based on one or more query parameter, tests key / value based on queryParametersToPrune / queryParametersToExcludePrune:nuxt.config
- you can also specify routes in , ex. { generate: { routes: [ '/?prune=true' ] } } )headers-exist
- : prune based on one or more header, tests key / value based on headersToPrune / headersToExcludePrune.
N.B.: It's possibile to mix different types.
___
- Nuxt hooks, the plugin has access to request.headers only if the project is running as a server (ex. nuxt start)generate
- If you your site it's not possibile to check request.headers, so (for types: [ 'default-detect', 'headers-exist' ]) it always prune, but You can disable this behavior by setting hookGeneratePage to false (or by using the type query-parameters);types: [ 'default-detect' ]
- Usage with , load the MobileDetect library;
- It use Cheerio, jQuery for servers, library to filter and prune the html.
___
- Before setting up the module, try to Disable JavaScript With Chrome DevTools while navigate your website, this is how your website appear (when nuxt-prune-html is enabled);
- For components
- You can check the website as a GoogleBot, following this guide.
___
1. Clone the repository:
- git clone https://github.com/LuXDAmore/nuxt-prune-html.git;yarn install
2. Install dependencies:
- (or npm install);yarn dev
3. Start a development server:
- (or npm run dev);yarn generate
4. Extra, generate the documentation (Github Pages):
- (or npm run generate);/docs` folder_.
- _the content is automatically generated into the
Please make sure to read the issue reporting checklist before opening an issue.
Issues not conforming to the guidelines may be closed immediately.
Please make sure to read the contributing guide before making a pull request.
Details changes for each release are documented in the release notes.
MIT License // Copyright (©) 2019-present Luca Iaconelli
#### 💼 Hire me

#### 💸 Are you feeling generous today?
If You want to share a beer, we can be really good friends 😄
__[Paypal][paypal-donate-href] // [Patreon][patreon-donate-href] // [Ko-fi][kofi-donate-href]__
> ☀ _It's always a good day to be magnanimous_ - cit.