nuxt-prune-html
                                
                                 nuxt-prune-html copied to clipboard
                                
                                    nuxt-prune-html copied to clipboard
                            
                            
                            
                        🔌⚡ 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...
🔌⚡ 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 routingonclient-sidefor bots and audits;
- No hydrationonclient-sidefor bots and audits:- ex. vue-lazy-hydrationneed Javascript client-side code to trigger hydrateOnInteraction, hydrateWhenIdle or hydrateWhenVisible;
 
- ex. 
- No <client-only>components;
- Can break lazy-loadfor 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-loadcan be fixed with a native attribute, with a customscriptor 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;
- Hydrationdecrease 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
 

Setup
- Install @luxdamore/nuxt-prune-htmlas a dependency:- yarn add @luxdamore/nuxt-prune-html;
- or, npm install --save @luxdamore/nuxt-prune-html;
 
- Append @luxdamore/nuxt-prune-htmlto themodulesarray 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;
- auditUserAgentor- matchUserAgent, trigger- .match()method;
 
 
- different checks with MobileDetect:
- query-parameters: prune based on one or more query parameter, tests- key / valuebased on- queryParametersToPrune / 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, tests- key / valuebased on- headersToPrune / headersToExcludePrune.
N.B.: It's possibile to mix different types.
Related things you should know
- Nuxt hooks, the plugin has access to request.headersonly if the project is running as a server (ex.nuxt start)- If you generateyour 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 settinghookGeneratePagetofalse(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;
- The nuxt-apis-to-file module can help you with data payload extraction during the build time.
👩🏻💻👨🏻💻 Development
- Clone the repository:
- git clone https://github.com/LuXDAmore/nuxt-prune-html.git;
 
- Install dependencies:
- yarn install(or- npm install);
 
- Start a development server:
- yarn dev(or- npm run dev);
 
- Test your code:
- yarn test(or- npm run test);
 
- Extra, generate the documentation (Github Pages):
- yarn generate(or- npm run generate);
- the content is automatically generated into the /docsfolder.
 
🐞 Issues
Please make sure to read the issue reporting checklist before opening an issue. Issues not conforming to the guidelines may be closed immediately.
📝 Discussions
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.
👥 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-now 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.