Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contents not rendered at server-side with nuxt. #1917

Open
maninak opened this issue May 19, 2022 · 20 comments
Open

Contents not rendered at server-side with nuxt. #1917

maninak opened this issue May 19, 2022 · 20 comments
Labels
enhancement New feature or request

Comments

@maninak
Copy link

maninak commented May 19, 2022

Hello,

thanks for the great plugin!

It seems that when doing static site generation (SSG) on the server-side (I suspect it also applies for the SSR use case), any HTML injected into the dom via dompurify-html will not be present in the pre-rendered HTML.

Sure, the content will be added to the DOM after the initial page visit at hydration time, but that causes multiple layout shifts as content come into the page increasing our CLS performance metric massively, not only offering a worse experience to the users but also affecting our SEO ranking. Another (possible) SEO hit comes more directly because the original HTML is missing crucial content.

FYI I've already seen this closed MR #591

Tested with vue-dompurify-html v2.5.0

@LeSuisse
Copy link
Owner

Hi,

Yes this is expected at this stage, this why the documentation ask to load it on the client side: https://github.com/LeSuisse/vue-dompurify-html/tree/vue-legacy#usage-with-nuxt

To make it work, it requires to initialize DOMPurify with JSDOM since there is no DOM to manipulate when running server side.

I will take a look to make possible to choose how DOMPurify is initialized so it is possible to get a DOMPurify instance with JSDOM and publish a proper Nuxt module to ease the installation/setup process.

That being said I'm not sure to understand the use case of DOMPurify/vue-dompurify-html in a SSG scenario. Do you not control all the inputs in this situation?

@maninak
Copy link
Author

maninak commented May 19, 2022

Hey (and thanks for responding so quickly!),

here's a simplified way of how I'm using it in a vue component

<template>
  <article>
    <div dompurify-html="item.richtext"></div>
  </article>
</template>

and of course, following the docs, it's added on nuxt.config.js like so:

{
  //...
  plugins: [
    // ...
    { src: '~/plugins/vue-dompurify-html', mode: 'client' },
  ],
}

I'm not sure to understand the use case of DOMPurify/vue-dompurify-html in a SSG scenario. Do you not control all the inputs in this situation?

If I understand your question correctly, then yes, I control the input (here item.richtext) which contains rich text content in the form of HTML. Item is coming from a headless CMS BE (Strapi in this case). During nuxt build with static: true to enable SSG, the data for item will normally be fetched at build time and be used to pre-render the .html file for that page. But that div with the dompurify-html directive will be empty in the generated HTML causing the issues I described in my original post.

I think I'm doing everything the standard way. Please let me know if I should do things differently or if there's a way to fix my issue. Also, let me know if there's any more info that would be helpful.

@LeSuisse LeSuisse added the enhancement New feature or request label May 19, 2022
@LeSuisse
Copy link
Owner

If I understand your question correctly, then yes, I control the input (here item.richtext) which contains rich text content in the form of HTML. Item is coming from a headless CMS BE (Strapi in this case). During nuxt build with static: true to enable SSG, the data for item will normally be fetched at build time and be used to pre-render the .html file for that page. But that div with the dompurify-html directive will be empty in the generated HTML causing the issues I described in my original post.

OK got it, it makes sense to me now. You are pulling untrusted data at build time.

I think I'm doing everything the standard way. Please let me know if I should do things differently or if there's a way to fix my issue. Also, let me know if there's any more info that would be helpful.

For now the only solution is to use it client side.

@maninak
Copy link
Author

maninak commented May 19, 2022

Understood. Thanks for letting me know. I'll watch this issue in case the feature is added in the future.

It's worth sharing here, that many (most?) nuxt users fetch page data from a CMS, which very often contains HMTL (as rich text that non-technical CMS users author on a CMS word-like text editor) that needs to be injected into the page and be present at server-side generation.

And of course, given that injected HTML is expected to be everywhere for CMS-driven websites, sanitization on every page is a no-brainer.

So I'm impressed this issue hasn't come up before, because it sounds to me that my use case should be the main "target group" of this plugin. I could be wrong of course.

Thanks for taking the time to respond! 🙏

LeSuisse added a commit that referenced this issue Jun 5, 2022
This make possible for server-side users (e.g. Nuxt) to instantiate
DOMPurify with JSDOM.

See #1917.
LeSuisse added a commit that referenced this issue Jun 5, 2022
This make possible for server-side users (e.g. Nuxt) to instantiate
DOMPurify with JSDOM.

See #1917.
LeSuisse added a commit that referenced this issue Jun 5, 2022
This make possible for server-side users (e.g. Nuxt) to instantiate DOMPurify with JSDOM.

The README has been update to describe the setup.

See #1917.
LeSuisse added a commit that referenced this issue Jun 5, 2022
This make possible for server-side users (e.g. Nuxt) to instantiate DOMPurify with JSDOM.

The README has been update to describe the setup.

See #1917.
@LeSuisse
Copy link
Owner

LeSuisse commented Jun 5, 2022

Hi,

I did some changes to expose the necessary primitive so the directive can be also used on the server side. You can see the setup here: https://github.com/LeSuisse/vue-dompurify-html/tree/vue-legacy#server-side

I will take a look to provide a Nuxt module for the v3 to make the setup easier.

@osroca
Copy link

osroca commented Aug 31, 2022

Hi @LeSuisse, I'm trying to implement this solution with nuxt v2.15.8 and vue v2.6.14. As @maninak I'm using Strapi in my project and I'm using also richText components, so I'm pulling HTML at build time.
I'm sorry but it's not clear to me how to implement the server-side directive. I'm not sure if I'm importing the function from the right module
this is my implementation:

nuxt.config.js

import DOMPurify from 'dompurify'
import { buildVueDompurifyHTMLDirective } from 'vue-dompurify-html'
...
render: {
    bundleRenderer: {
      directives: {
        'dompurify-html': (el, dir) => {
          const insertHook = buildVueDompurifyHTMLDirective({}, () => {
            const window = new JSDOM('').window
            return DOMPurify(window)
          }).inserted
          insertHook(el, dir)
          el.data.domProps = { innerHTML: el.innerHTML }
        },
      },
    },
  }

BTW, I also had to extend nuxt's webpack to load the vue-dompurify-html mjs module due to this error:

[develop:frontend]  ERROR  in ./node_modules/vue-dompurify-html/dist/vue-dompurify-html.mjs
[develop:frontend]
[develop:frontend] Can't import the named export 'isVue3' from non EcmaScript module (only default export is available)

I extended webpack config with: (in nuxt.config.js)

 ...
  build: {
    extend(config, ctx) {
      config.module.rules.push({
        test: /\.mjs$/,
        include: /node_modules/,
        type: 'javascript/auto',
      })
    },
  },
...

I'm arrived to this issue looking for a solution of this error:

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.

image

This error doesn't happen if I don't use the $md.render(richText) within the v-dompurify-html directive.

Please, can you tell me what I'm doing wrong?

@LeSuisse
Copy link
Owner

LeSuisse commented Sep 5, 2022

The import issue are likely caused by the v3 since we are now also publishing ESM with the package.

That being said I'm a bit surprise you are using vue-dompurify-html v3 with Vue 2.6.14


I would suggest to try with vue-dompurify-html 2.6.0 to see if you have the same issue.

Anyway something might be broken with Nuxt and Vue 2.7 with vue-dompurify-html v3.
Personally I do not use Nuxt so I will check when I got some free time (likely end of this month, beginning of next one). Also, I still have the idea to publish a Nuxt module to ease the setup phase for Nuxt users.

@osroca
Copy link

osroca commented Sep 5, 2022

@LeSuisse thanks a lot for getting back to me.
I'll try with 2.6.0 and I'll let you know.
Please, let me know if having a repro repo would help you in the debug and I can share my project. It is a bit meshy because I'm starting with Stratpi but I hope it can help you.

@serialine
Copy link

serialine commented Oct 20, 2022

@osroca
I made an example of using modules in nuxt2. hope this helps you.
https://github.com/serialine/vue-dompurify-html/tree/example-nuxt2/examples/nuxt2
or #2257

@LeSuisse
Copy link
Owner

Thanks for the PR @serialine!

@Pecral
Copy link

Pecral commented Mar 6, 2023

@LeSuisse did you already try to get this to run in nuxt 3 when using SSR mode? It looks like the bundleRenderer is not as easily accessible anymore compared to Nuxt 2, though maybe I haven't found the right documentation, yet. I'll fiddle around in the next days, but I'd appreciate any information that you have. Thank you.

@LeSuisse
Copy link
Owner

LeSuisse commented Mar 6, 2023

No I did not but it seems it is possible to use getSSRProps so it should be do-able.

https://nuxt.com/docs/guide/directory-structure/plugins#vue-directives

@jakubkoje
Copy link

Nuxt 3 SSR support would be awesome.

@marcinkozaczyk
Copy link

I have used this code to create Nuxt 3 server plugin.
Place it in plugins/dompurify.server.ts file.

import { JSDOM } from "jsdom";
import createDOMPurify from "dompurify";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive("dompurify-html", {
    getSSRProps(binding) {
      const createDomPurifyInstance = () => {
        const window = new JSDOM("").window;
        return createDOMPurify(window);
      };
      const dompurifyInstance = createDomPurifyInstance();
      const innerHTML = dompurifyInstance.sanitize(binding.value);
      return {
        innerHTML,
      };
    },
  });
});

Note: I do not pass config/options/directive args to .sanitize function.

@Anoesj
Copy link

Anoesj commented Dec 11, 2023

Running into the same issue in Nuxt 3. It would indeed be very nice to get a Nuxt 3 plugin for this. I cannot get the example above by @marcinkozaczyk to work either. Elements using the directive just turn out empty after being server-side rendered, so I'm getting lots of hydration mismatches everywhere. If I add console.log(innerHTML) just before the return statement, I do see rendered HTML in the CLI, it's just not inserted into the element that uses the v-dompurify-html directive.

Might be related to this? vuejs/core#8112

@freezyh

This comment was marked as resolved.

@freezyh

This comment was marked as spam.

@LeSuisse
Copy link
Owner

Might be related to this? vuejs/core#8112

I think you are correct. I am not sure what possibilities we have (at least while keeping the directive API approach) to manage the server side rendering if we cannot manipulate the DOM via getSSRProps.

In the meantime I added a bit of documentation and example to at least cover the client side part: https://github.com/LeSuisse/vue-dompurify-html/tree/main/packages/vue-dompurify-html#usage-with-nuxt-3

@freezyh
Copy link

freezyh commented Dec 16, 2023

Might be related to this? vuejs/core#8112

I think you are correct. I am not sure what possibilities we have (at least while keeping the directive API approach) to manage the server side rendering if we cannot manipulate the DOM via getSSRProps.

In the meantime I added a bit of documentation and example to at least cover the client side part: https://github.com/LeSuisse/vue-dompurify-html/tree/main/packages/vue-dompurify-html#usage-with-nuxt-3

Thanks, but It not works and show error tips, you can view the nuxt issues:
nuxt/nuxt#13382
so I add the code :
// domPurify.server.ts
`import VueDOMPurifyHTML from 'vue-dompurify-html'

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueDOMPurifyHTML, {})
})
`

to solve the problem, It works!

but another problem appear, for example I will use highlight.js directive the sametime
my code:
1.<div v-highlight v-html="text2"></div>
2.<div v-highlight v-dompurify-html="text2"></div>
the one show normal, the two not normal , It will break hightlight.js structure, how to solve it!
微信图片_20231216151507

@filiphazardous
Copy link

I tried to be creative and solve this by parsing the sanitized HTML and supply it as childNodes in getSSRProps. That didn't work either :-(
(I might add that I replaced the functionality in updateComponent to remove old childNodes and append new childNodes as well - still didn't help.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

10 participants