{"id":13506717,"url":"https://github.com/LuXDAmore/nuxt-prune-html","last_synced_at":"2025-03-30T05:31:19.745Z","repository":{"id":40288993,"uuid":"245136422","full_name":"LuXDAmore/nuxt-prune-html","owner":"LuXDAmore","description":"🔌⚡ 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","archived":false,"fork":false,"pushed_at":"2023-09-15T15:02:20.000Z","size":10405,"stargazers_count":78,"open_issues_count":8,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-15T09:18:06.023Z","etag":null,"topics":["audit","bot","cheerio","dynamic-rendering","html","lighthouse","measure","modules","nuxt","nuxt-module","nuxtjs","optimization","optimization-algorithms","optimize","pagespeed-insights","performance","prune","pruning","vuejs","web-vitals"],"latest_commit_sha":null,"homepage":"https://luxdamore.github.io/nuxt-prune-html/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LuXDAmore.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null},"funding":{"patreon":"luxdamore","open_collective":"luca-iaconelli","ko_fi":"luxdamore","liberapay":"luxdamore","issuehunt":"luxdamore","otechie":"luxdamore","custom":["https://www.paypal.me/luxdamore"]}},"created_at":"2020-03-05T10:44:06.000Z","updated_at":"2024-10-30T06:14:42.000Z","dependencies_parsed_at":"2024-03-31T04:45:21.396Z","dependency_job_id":null,"html_url":"https://github.com/LuXDAmore/nuxt-prune-html","commit_stats":{"total_commits":278,"total_committers":3,"mean_commits":92.66666666666667,"dds":0.0539568345323741,"last_synced_commit":"13d11dc35d56b9c3081d3be087a7bd068a7a97a4"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuXDAmore%2Fnuxt-prune-html","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuXDAmore%2Fnuxt-prune-html/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuXDAmore%2Fnuxt-prune-html/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuXDAmore%2Fnuxt-prune-html/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LuXDAmore","download_url":"https://codeload.github.com/LuXDAmore/nuxt-prune-html/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246281099,"owners_count":20752203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit","bot","cheerio","dynamic-rendering","html","lighthouse","measure","modules","nuxt","nuxt-module","nuxtjs","optimization","optimization-algorithms","optimize","pagespeed-insights","performance","prune","pruning","vuejs","web-vitals"],"created_at":"2024-08-01T01:00:56.172Z","updated_at":"2025-03-30T05:31:18.701Z","avatar_url":"https://github.com/LuXDAmore.png","language":"JavaScript","funding_links":["https://patreon.com/luxdamore","https://opencollective.com/luca-iaconelli","https://ko-fi.com/luxdamore","https://liberapay.com/luxdamore","https://issuehunt.io/r/luxdamore","https://otechie.com/luxdamore","https://www.paypal.me/luxdamore","https://www.patreon.com/luxdamore"],"categories":["Uncategorized","JavaScript"],"sub_categories":["Uncategorized"],"readme":"# 🔌⚡ Nuxt Prune HTML\n\n[![Code Quality][quality-src]][quality-href]\n[![Downloads][npm-downloads-src]][npm-downloads-href]\n[![Dependencies][dependencies-src]][dependencies-href]\n[![Circle CI][circle-ci-src]][circle-ci-href]\n[![Version][npm-version-src]][npm-version-href]\n[![Donate][paypal-donate-src]][paypal-donate-href]\n\n[quality-src]: https://img.shields.io/badge/code%20quality-A-informational?style=flat\n[quality-href]: https://luxdamore.github.io/nuxt-prune-html/\n\n[npm-downloads-src]: https://img.shields.io/npm/dt/@luxdamore/nuxt-prune-html.svg?style=flat\u0026color=darkgreen\n[npm-downloads-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html\n\n[dependencies-src]: https://img.shields.io/badge/dependencies-up%20to%20date-darkgreen.svg?style=flat\n[dependencies-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html\n\n[circle-ci-src]: https://img.shields.io/circleci/project/github/LuXDAmore/nuxt-prune-html.svg?style=flat\u0026color=darkgreen\n[circle-ci-href]: https://circleci.com/gh/LuXDAmore/nuxt-prune-html\n\n[npm-version-src]: https://img.shields.io/npm/v/@luxdamore/nuxt-prune-html/latest.svg?style=flat\u0026color=darkorange\u0026label=version\n[npm-version-href]: https://npmjs.com/package/@luxdamore/nuxt-prune-html\n\n[paypal-donate-src]: https://img.shields.io/badge/paypal-donate-black.svg?style=flat\n[paypal-donate-href]: https://www.paypal.me/luxdamore\n[patreon-donate-href]: https://www.patreon.com/luxdamore\n[kofi-donate-href]: https://ko-fi.com/luxdamore\n\n\u003e 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.\n\n## 💘 Motivation\n\nDue 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.\n\nThis 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*).\nThis should **speed up** (**blazing fast**) your *nuxt-website* up to a value of **~99** in **performance** because it [cheats various scenarios](https://web.dev/lighthouse-performance/).\n\nUsually, with **less assets, resources and html** to download, the number of urls crawled by a bot are **widely boosted** 📈.\n\n\u003e Inspired by this [rcfs](https://github.com/nuxt/rfcs/issues/22) and this [issue](https://github.com/nuxt/nuxt.js/issues/2822).\n\n### Features\n\n- Prune based on **default detection**;\n  - match the **user-agent**;\n  - match a **bot**;\n  - match an **audit**;\n  - match a **custom-header**;\n- Prune based on **headers values** (*useful in/for Lambdas*);\n- Prune based on **query parameters** (*useful during navigation, hybrid-experience*).\n\n### Pro et contra\n\n\u003e This could cause some unexpected behaviors, but..\n\n**Cons.:**\n\n- No [`SPA routing`](https://nuxtjs.org/docs/2.x/concepts/server-side-rendering/#server-side-rendering-steps-with-nuxtjs) on `client-side` for **bots and audits**;\n- No [`hydration`](https://ssr.vuejs.org/guide/hydration.html) on `client-side` for **bots and audits**:\n  - ex. [`vue-lazy-hydration`](https://github.com/maoberlehner/vue-lazy-hydration) need **Javascript client-side code** to trigger _hydrateOnInteraction_, _hydrateWhenIdle_ or _hydrateWhenVisible_;\n- No [`\u003cclient-only\u003e` components](https://nuxtjs.org/api/components-client-only/);\n- Can break `lazy-load` for images.\n\n**Pros.:**\n\n- Some of these features **aren't \"used by\"** a bot/audit, so you don't really need them:\n  - bots doesn't handle `SPA routing`;\n  - [`\u003cclient-only\u003e` components](https://nuxtjs.org/api/components-client-only/) could lead in a slower TTI;\n  - [`\u003cclient-only\u003e` components](https://nuxtjs.org/api/components-client-only/) can contain a [static placeholder](https://nuxtjs.org/api/components-client-only/);\n- Images with `lazy-load` can be fixed with a [native attribute](https://web.dev/native-lazy-loading/), with a custom `script` or with `classesSelectorsToKeep` (_check the configuration_);\n- 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](https://github.com/LuXDAmore/nuxt-apis-to-file), **speeding up** the website loading time;\n- `Hydration` **decrease** performance, so it's ok to prune it for `bots or audits`;\n- **Less HTML, assets and resources** are served to browsers and clients;\n- Bot/audit only have *the Javascript they need*;\n- With **less assets** to download, the number of urls crawled are **widely boosted**;\n- Bots, [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/), [Google Measure](https://web.dev/measure/) and [Lighthouse Audit](https://developers.google.com/web/tools/lighthouse) are already pruned by the plugin with the default configuration;\n- Faster [web-vitals](https://web.dev/vitals/), faster TTI, faster FCP, faster FMP, **faster all**.\n\n**N.B.:** _This is known as [Dynamic Rendering](https://developers.google.com/search/docs/guides/dynamic-rendering) and **it's not** considered [black-hat](https://www.wordstream.com/black-hat-seo) or [cloaking](https://en.wikipedia.org/wiki/Cloaking)_.\n\n___\n\n#### 💡 Lighthouse\n\n![Lighthouse Audit before](./src/static/lighthouse/before.jpg)\n![Lighthouse Audit after](./src/static/lighthouse/after.jpg)\n\n___\n\n## Setup\n\n1. **Install** `@luxdamore/nuxt-prune-html` as a dependency:\n   - `yarn add @luxdamore/nuxt-prune-html`;\n   - or, `npm install --save @luxdamore/nuxt-prune-html`;\n2. **Append** `@luxdamore/nuxt-prune-html` to the `modules` array of your `nuxt.config.js`.\n\n## Configuration\n\n```js\n\n    // nuxt.config.js\n    export default {\n\n        // Module - installation\n        modules: [ '@luxdamore/nuxt-prune-html' ],\n\n        // Module - default config\n        pruneHtml: {\n            enabled: false, // `true` in production\n            hideGenericMessagesInConsole: false, // `false` in production\n            hideErrorsInConsole: false, // deactivate the `console.error` method\n            hookRenderRoute: true, // activate `hook:render:route`\n            hookGeneratePage: true, // activate `hook:generate:page`\n            selectors: [\n                // CSS selectors to prune\n                'link[rel=\"preload\"][as=\"script\"]',\n                'script:not([type=\"application/ld+json\"])',\n            ],\n            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__)`\n            link: [], // inject custom links, only if pruned\n            script: [], // inject custom scripts, only if pruned\n            htmlElementClass: null, // a string added as a class to the \u003chtml\u003e element if pruned\n            cheerio: {\n                // the config passed to the `cheerio.load(__config__)` method\n                xmlMode: false,\n            },\n            types: [\n                // it's possibile to add different rules for pruning\n                'default-detect',\n            ],\n\n            // 👇🏻 Type: `default-detect`\n            headerNameForDefaultDetection: 'user-agent', // The `header-key` base for `MobileDetection`, usage `request.headers[ headerNameForDefaultDetection ]`\n            auditUserAgent: 'lighthouse', // prune if `request.header[ headerNameForDefaultDetection ]` match, could be a string or an array of strings\n            isAudit: true, // remove selectors if match with `auditUserAgent`\n            isBot: true, // remove selectors if is a bot\n            ignoreBotOrAudit: false, // remove selectors in any case, not depending on Bot or Audit\n            matchUserAgent: null, // prune if `request.header[ headerNameForDefaultDetection ]` match, could be a string or an array of strings\n\n            // 👇🏻 Type: 'query-parameters'\n            queryParametersToPrune: [\n                // array of objects (key-value)\n                // trigger the pruning if 'query-parameters' is present in `types` and at least one value is matched, ex. `/?prune=true`\n                {\n                    key: 'prune',\n                    value: 'true',\n                },\n            ],\n            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`\n\n            // 👇🏻 Type: 'headers-exist'\n            headersToPrune: [], // same as `queryParametersToPrune`, but it checks `request.headers`\n            headersToExcludePrune: [], // same as `queryParamToExcludePrune`, but it checks `request.headers`, this priority is over than `headersToPrune`\n\n            // Emitted events for callbacks methods\n            onBeforePrune: null, // ({ result, [ req, res ] }) =\u003e {}, `req` and `res` are not available on `nuxt generate`\n            onAfterPrune: null, // ({ result, [ req, res ] }) =\u003e {}, `req` and `res` are not available on `nuxt generate`\n        },\n\n    };\n\n```\n\nWith `link` and `script` it's possibile to add one or more objects on the pruned HTML, ex.:\n\n```javascript\n\n    export default {\n        pruneHtml: {\n            link: [\n                {\n                    rel: 'preload',\n                    as: 'script',\n                    href: '/my-custom-lazy-load-for-bots.js',\n                    position: 'phead', // Default value is 'body', other allowed values are: 'phead', 'head' and 'pbody'\n                },\n                {\n                    rel: 'stylesheet',\n                    href: '/my-custom-styles-for-bots.css',\n                    position: 'head',\n                },\n            ],\n            script: [\n                {\n                    src: '/my-custom-lazy-load-for-bots.js',\n                    lazy: true,\n                    defer: true,\n                },\n            ],\n        },\n    };\n\n```\n\n\u003e **N.B.:** _the config is only shallow merged, not deep merged_.\n\n### Types / Rules\n\nPossible values are `[ 'default-detect', 'query-parameters', 'headers-exist' ]`:\n\n- `default-detect`: prune based on **one header**(`request.headers[ headerNameForDefaultDetection ]`)\n  - different checks with [MobileDetect](https://hgoebl.github.io/mobile-detect.js/):\n    - `isBot`, trigger `.is( 'bot' )` method;\n    - `auditUserAgent` or `matchUserAgent`, trigger `.match()` method;\n- `query-parameters`: prune based on **one or more query parameter**, tests `key / value` based on `queryParametersToPrune / queryParametersToExcludePrune`:\n  - you can also specify routes in `nuxt.config`, ex. *`{ generate: { routes: [ '/?prune=true' ] } }`*\n- `headers-exist`: prune based on **one or more header**, tests `key / value` based on `headersToPrune / headersToExcludePrune`.\n\nN.B.: *It's possibile to mix different types.*\n\n___\n\n### Related things you should know\n\n- Nuxt [hooks](https://nuxtjs.org/api/configuration-hooks/), the plugin has access to `request.headers` only if the project is **running as a server** (ex. `nuxt start`)\n  - If you `generate` 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`);\n- Usage with `types: [ 'default-detect' ]`, load the [MobileDetect](https://hgoebl.github.io/mobile-detect.js/) library;\n- It use [Cheerio](https://github.com/cheeriojs/cheerio), *jQuery for servers*, library to **filter and prune** the html.\n\n___\n\n### Advices\n\n- Before setting up the module, try to [Disable JavaScript With Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/javascript/disable) while navigate your website, **this is how your website appear (when *nuxt-prune-html* is enabled)**;\n- For [`\u003cclient-only\u003e` components](https://nuxtjs.org/api/components-client-only/) you should prepare a version that is visually the same with the [placeholder slot](https://nuxtjs.org/api/components-client-only/);\n- You can check the website as a *GoogleBot*, following [this guide](https://developer.chrome.com/docs/devtools/device-mode/override-user-agent/);\n- The [nuxt-apis-to-file](https://github.com/LuXDAmore/nuxt-apis-to-file) module can help you with **data payload extraction** during the build time.\n\n___\n\n## 👩🏻‍💻👨🏻‍💻 Development\n\n1. **Clone** the repository:\n   - `git clone https://github.com/LuXDAmore/nuxt-prune-html.git`;\n2. **Install** dependencies:\n   - `yarn install` (or `npm install`);\n3. **Start** a development server:\n   - `yarn dev` (or `npm run dev`);\n4. **Test** your code:\n   - `yarn test` (or `npm run test`);\n5. **Extra**, generate the documentation ([*Github Pages*](https://pages.github.com/)):\n   - `yarn generate` (or `npm run generate`);\n   - _the content is automatically generated into the `/docs` folder_.\n\n## 🐞 Issues\n\nPlease make sure to read the [**issue reporting checklist**](./.github/ISSUE_TEMPLATE/bug_report.md) before opening an issue.\n*Issues not conforming to the guidelines may be closed immediately*.\n\n## 📝 Discussions\n\nWe're using [**Github discussions**](https://github.com/LuXDAmore/nuxt-prune-html/discussions) as a place to connect with other members of our community.\n*You are free to ask questions and share ideas, so enjoy yourself*.\n\n## 👥 Contribution\n\nPlease make sure to read the [**contributing guide**](./.github/ISSUE_TEMPLATE/feature_request.md) before making a pull request.\n\n## 📖 Changelog\n\nDetails changes for each release are documented in the [**release notes**](./CHANGELOG.md).\n\n### 🆓 License\n\n[MIT License](./LICENSE) // Copyright (©) 2019-now [Luca Iaconelli](https://lucaiaconelli.it)\n\n#### 💼 Hire me\n\n[![Contacts](https://img.shields.io/badge/Contact%20Me-Let's%20Talk-informational?style=social\u0026logo=minutemailer)](https://curriculumvitae.lucaiaconelli.it)\n\n#### 💸 Are you feeling generous today?\n\nIf You want to share a beer, we can be really good friends 😄\n\n__[Paypal][paypal-donate-href] // [Patreon][patreon-donate-href] // [Ko-fi][kofi-donate-href]__\n\n\u003e ☀ _It's always a good day to be magnanimous_ - cit.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLuXDAmore%2Fnuxt-prune-html","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLuXDAmore%2Fnuxt-prune-html","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLuXDAmore%2Fnuxt-prune-html/lists"}