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 📈.
- 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
onclient-side
for bots and audits; - No
hydration
onclient-side
for bots and audits:- ex.
vue-lazy-hydration
need Javascript client-side code to trigger hydrateOnInteraction, hydrateWhenIdle or hydrateWhenVisible;
- ex.
- No
<client-only>
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
; <client-only>
components could lead in a slower TTI;<client-only>
components can contain a static placeholder;
- bots doesn't handle
- Images with
lazy-load
can be fixed with a native attribute, with a customscript
or withclassesSelectorsToKeep
(check the configuration); - You can pre-load ad inject all of the data that you need (Rest API, GraphQL, ecc) during the build phase with nuxt-apis-to-file, speeding up the website loading time;
Hydration
decrease performance, so it's ok to prune it forbots 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.
- Install
@luxdamore/nuxt-prune-html
as a dependency:yarn add @luxdamore/nuxt-prune-html
;- or,
npm install --save @luxdamore/nuxt-prune-html
;
- Append
@luxdamore/nuxt-prune-html
to themodules
array of yournuxt.config.js
.
// nuxt.config.js
export default {
// Module - installation
modules: [ '@luxdamore/nuxt-prune-html' ],
// Module - default config
pruneHtml: {
enabled: false, // `true` in production
hideGenericMessagesInConsole: false, // `false` in production
hideErrorsInConsole: false, // deactivate the `console.error` method
hookRenderRoute: true, // activate `hook:render:route`
hookGeneratePage: true, // activate `hook:generate:page`
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 `classesSelectorsToKeep` is appended to every `selectors`, ex.: `link[rel="preload"][as="script"]:not(__classesSelectorsToKeep__)`
link: [], // inject custom links, only if pruned
script: [], // inject custom scripts, only if pruned
htmlElementClass: null, // a string added as a class to the <html> element if pruned
cheerio: {
// the config passed to the `cheerio.load(__config__)` method
xmlMode: false,
},
types: [
// it's possibile to add different rules for pruning
'default-detect',
],
// 👇🏻 Type: `default-detect`
headerNameForDefaultDetection: 'user-agent', // The `header-key` base for `MobileDetection`, usage `request.headers[ headerNameForDefaultDetection ]`
auditUserAgent: 'lighthouse', // prune if `request.header[ headerNameForDefaultDetection ]` match, could be a string or an array of strings
isAudit: true, // remove selectors if match with `auditUserAgent`
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 `request.header[ headerNameForDefaultDetection ]` 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=true`
{
key: 'prune',
value: 'true',
},
],
queryParametersToExcludePrune: [], // same as `queryParametersToPrune`, 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.headers`
headersToExcludePrune: [], // same as `queryParamToExcludePrune`, 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 generate`
onAfterPrune: null, // ({ result, [ req, res ] }) => {}, `req` 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.:
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 ]
)- different checks with MobileDetect:
isBot
, trigger.is( 'bot' )
method;auditUserAgent
ormatchUserAgent
, trigger.match()
method;
- different checks with MobileDetect:
query-parameters
: prune based on one or more query parameter, testskey / value
based onqueryParametersToPrune / queryParametersToExcludePrune
:- you can also specify routes in
nuxt.config
, ex.{ generate: { routes: [ '/?prune=true' ] } }
- you can also specify routes in
headers-exist
: prune based on one or more header, testskey / value
based onheadersToPrune / 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
)- If you
generate
your site it's not possibile to check request.headers, so (fortypes: [ 'default-detect', 'headers-exist' ]
) it always prune, but You can disable this behavior by settinghookGeneratePage
tofalse
(or by using the typequery-parameters
);
- If you
- Usage with
types: [ 'default-detect' ]
, 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
<client-only>
components you should prepare a version that is visually the same with the placeholder slot; - You can check the website as a GoogleBot, following this guide;
- The nuxt-apis-to-file module can help you with data payload extraction during the build time.
- Clone the repository:
git clone https://github.com/LuXDAmore/nuxt-prune-html.git
;
- Install dependencies:
yarn install
(ornpm install
);
- Start a development server:
yarn dev
(ornpm run dev
);
- Test your code:
yarn test
(ornpm run test
);
- Extra, generate the documentation (Github Pages):
yarn generate
(ornpm run generate
);- the content is automatically generated into the
/docs
folder.
Please make sure to read the issue reporting checklist before opening an issue. Issues not conforming to the guidelines may be closed immediately.
We're using Github discussions as a place to connect with other members of our community. You are free to ask questions and share ideas, so enjoy yourself.
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-now Luca Iaconelli
If You want to share a beer, we can be really good friends 😄
☀ It's always a good day to be magnanimous - cit.