π β‘ Nuxt Prune HTML
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.
π Motivation
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
Features
- 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).
Pro et contra
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); -
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.
π‘ Lighthouse
Setup
-
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
.
Configuration
// 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.
Types / Rules
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.
Related things you should know
- 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.
Advices
- 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.
π©π»βπ»π¨π»βπ» Development
-
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
);
-
-
Extra, generate the documentation (Github Pages):
-
yarn generate
(ornpm run generate
); -
the content is automatically generated into the
/docs
folder.
-
π Issues
Please make sure to read the issue reporting checklist before opening an issue. Issues not conforming to the guidelines may be closed immediately.
π₯ Contribution
Please make sure to read the contributing guide before making a pull request.
π Changelog
Details changes for each release are documented in the release notes.
π License
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
β It's always a good day to be magnanimous - cit.