vue icon indicating copy to clipboard operation
vue copied to clipboard

Allow <noscript> in Vue templates for SSR usage

Open brophdawg11 opened this issue 5 years ago • 31 comments

Version

2.5.17

Reproduction link

https://codepen.io/brophdawg11/pen/OBdZyX

Steps to reproduce

I'm opening this issue as a follow up to https://github.com/vuejs/vue/issues/8247, as I don't think the solution provided there is suitable for all use-cases. In a fairly simple UI, where everything is relatively positioned and flows downward, it would likely be fine to include <noscript> outside the context of the Vue application, and it would render correctly above the entire app.

However, there are plenty of other UI's that it may not be desirable or feasible to include <noscript> outside of the Vue application context and display it properly. The linked codepen shows a simple fixed-header layout, where including the <noscript> tag outside the Vue application context results in the <noscript> tag being hidden behind the fixed header, where in reality it is intended to be rendered inside the main body content, and thus below the fixed header.

The <noscript> outside the Vue context also has the unintended effect of pushing down the main content, which has a proper margin-top to account for the static-height fixed header.

Per MDN, <noscript> is Flow Content (https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Flow_content), it is perfectly viable to exist outside the <head> (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript), and is perfectly valid to nest inside the DOM in a <div>, as div's allow Flow Content as their children (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div).

Please reconsider the decision to not permit noscript tags in Vue templates.

What is expected?

<noscript> elements should render properly in Vue templates

What is actually happening?

<noscript> elements do not render properly in Vue templates and cause hydration issues when in SSR

brophdawg11 avatar Oct 26 '18 17:10 brophdawg11

As a workaround, we are currently doing the following, but it feels quite hackish:

<template> 
    <main>
        <div v-once v-html="noscriptHtml" />
        ...
    </main
</template>

<script>
export default {
   ...
    created() {
        this.noscriptHtml = '<noscript>...</noscript>';
    }
};
</script>

brophdawg11 avatar Oct 26 '18 17:10 brophdawg11

I don't know, but I think it doesn't make sense.

<noscript> is for disabled Javascript on the browser. And, Vue apps are just Javascript. So, if Javascript is disabled, Vue templates don't works.

So, <noscript> needs to stay out of Vue and should be in vanilla HTML.

alexbruno avatar Oct 26 '18 17:10 alexbruno

Sorry, it's not super clear in the description here - but the linked/closed issue is specific to using SSR with Vue, as is my case. With JavaScript disabled - we still SSR a perfectly valid page.

The problem is that we include noscript tags for when the browser doesn't have JavaScript enabled. But Vue's inability to render them client side breaks browsers that do have JavaScript enabled (insofar as hydration fails and a full re-render is performed).

brophdawg11 avatar Oct 27 '18 01:10 brophdawg11

Ok, even with SSR, Vue components are not place for <noscript>. If you want to prevent errors when browser Javascript is disabled, write a global <noscript> in the index.html.

If you want to show an user friendly content, put a <meta> redirect into <noscript> and head to a static vanilla HTML page.

It makes more sense.

alexbruno avatar Oct 28 '18 00:10 alexbruno

I disagree. What the code pen is showing is that with a fixed header UX, it is not feasible to put the noscript tag in the index.html template, completely outside of the Vue app. It must be part of the app in order to be displayed to the user in a meaningful location.

And redirecting to a separate page defeats the purpose of allowing users with JS disabled to browse and use the site. If the site is already 100% user friendly with JS disabled, why would we redirect instead of showing a message that some advanced UI functionality might not work, but core browsing and usage will be fine.

What is the reasoning behind Vue templates supporting only a subset of valid HTML markup? One of the major advantages of SSR is that is opens up the possibility of using Vue and not giving up support for JS disabled users.

brophdawg11 avatar Oct 28 '18 02:10 brophdawg11

Using the redirect described above would also not be very good for SEO

tmorehouse avatar Oct 29 '18 15:10 tmorehouse

I don't see why <noscript> shouldn't be allowed in vue.

Obviously, it makes no sense in the client-side but if you are using SSR to provide html & css to the client, it's an incredibly useful feature

AlbertMarashi avatar Dec 21 '18 02:12 AlbertMarashi

@alexbruno It makes sense to have noscript in component in some case : like SEO (one of the main purpose of SSR provided by Vue).

If you lazy-loading image you can have a component like that :

<div class="lazy-image-component">
  <img :src="inView && src"  />

  <noscript inline-template>
    <img :src="src" />
  </noscript>
</div>

In this case it's important for the img tag contain in noscript to be at this place for better UI integration.

lanaambre avatar Mar 12 '19 11:03 lanaambre

One small caveat, should anybody come here in search for a solution for noscript. The children of the <noscript/> are not rendered if it is inside a conditional.

<div>
    <noscript inline-template>
        <span>OMG</span>
    </noscript>
</div>

sends <noscript><span>OMG</span></noscript> to the client

<div v-if="someTrueCondition">
    <noscript inline-template>
        <span>OMG</span>
    </noscript>
</div>

sends an empty <noscript> tag.

mwidmann avatar May 24 '19 09:05 mwidmann

What's the status on this? I would love some workaround for this that's less hacky.

srcrip avatar Nov 24 '19 20:11 srcrip

@Sevensidedmarble @brophdawg11

Here is a simple workaround using Vue's <component> component (which doesn't feel too "hacky"):

<component is="noscript">
  <p>No Script Content Here</p>
</component>

This will let you get around Vue's constraints on the <noscript> tag (also works for rendering <style> tags as well)

You could even make it conditional for rendering on the server generated pages only (and clients/cralwers with javascript disabled will still see it):

<component v-if="$isServer" is="noscript">
  <p>No Script Content Here</p>
</component>

tmorehouse avatar Nov 24 '19 20:11 tmorehouse

It absolutely makes sense to include <noscript> alternatives inside Vue components. Each component encapsualtes a defined piece of functionality, so where better to put the noscript alternative than inside that specific component. e.g.

<form action="">
    ...
    <button @click="ajaxSubmit">Submit</button>

    <noscript>
        <input type="button">Submit</input>
    </noscript>
</form>

Otherwise you will need to have a duplicate set of components/HTML/CSS in a different part of the codebase for users that have JS disabled.

lukenofurther avatar Mar 11 '20 14:03 lukenofurther

It absolutely makes sense to include <noscript> alternatives inside Vue components. Each component encapsualtes a defined piece of functionality, so where better to put the noscript alternative than inside that specific component. e.g.

<form action="">
    ...
    <button @click="ajaxSubmit">Submit</button>

    <noscript>
        <input type="button">Submit</input>
    </noscript>
</form>

Otherwise you will need to have a duplicate set of components/HTML/CSS in a different part of the codebase for users that have JS disabled.

@lukenofurther your example could be solved without the need for <noscript>

<form action="">
    ...
    <button type="submit" @click.prevent="ajaxSubmit">Submit</button>
</form>

that said, I totally agree <noscript> should be allowed. @tmorehouse 's solution worked well for my needs though.

spacedawwwg avatar Apr 17 '20 07:04 spacedawwwg

@lukenofurther your example could be solved without the need for <noscript>

<form action="">
    ...
    <button type="submit" @click.prevent="ajaxSubmit">Submit</button>
</form>

@spacedawwwg ok thanks for that, it was a simple contrived example I plucked out of nowhere though. There are real-world examples that can't be so easily solved without noscript tags.

that said, I totally agree <noscript> should be allowed. @tmorehouse 's solution worked well for my needs though.

I'm really glad @tmorehouse's solution works too, thank you @tmorehouse! It is still hack though, and noscript tags should be supported in the core library or at least the server renderer.

There are plenty of people on this issue alone who genuinely need it. If you want a real-world example, take a look at the markup on bbc.co.uk and you'll see loads of noscript tags that provide no-JS fallbacks deeply embedded in what look to be React components.

image

At the moment, the lack of this functionality out of the box is impeding Vue developers from achieving modern accessibility standards. On our project we're using noscripts to achieve WCAG level AA, which isn't even the most difficult level to achieve.

lukenofurther avatar Apr 17 '20 09:04 lukenofurther

None of these are necessary.

<form @submit.prevent='ajaxSubmit'>
  ...
  <button type='submit'>Submit</submit>
</form>

If JS is enabled, Vue capture the event. Else, native HTML action will happen.

alexbruno avatar Apr 22 '20 14:04 alexbruno

@alexbruno that's not the point - I made that example up out of my head, it's not a real world example and it's very simple. There are other situations that can't be solved so easily with that kind of workaround.

lukenofurther avatar Apr 23 '20 20:04 lukenofurther

Here is a quick component for creating a <noscript> (as an alternative to using <component is="noscript">).

Note that render functions allow you to use tags that vue-loader normally filters out:

export default {
  name: 'NoScript',
  functional: true,
  render(h, { data, children }) {
    return h('noscript', data, children)
  }
}

And then use it like so:

<no-script>
  <p>JavaScript you have not. Hmmm.</p>
</no-script>

tmorehouse avatar Apr 23 '20 21:04 tmorehouse

@alexbruno - another example of where noscript is required is with an SSR and infinite scrolling with none JS pagination as a fallback (as per Google guidelines). Enabling noscript like in the following example would be a massive help as the contents of the noscript is rendered by Vue.

<infinite-loading @infinite="infiniteHandler" spinner="spiral">
    ...
</infinite-loading>
<noscript><Pager :info="$page.posts.pageInfo" /></noscript>

Unfortunately @tmorehouse solution doesn't work in our case.

dozyio avatar May 14 '20 13:05 dozyio

@dozyio you could have a <div> rendered SSR, and then when mounted set it's display to none:

<template>
  <infinite-loading @infinite="infiniteHandler" spinner="spiral">
    ...
  </infinite-loading>
  <div v-show="showPager">
    <Pager :info="$page.posts.pageInfo" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      showPager: true
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.showPager = false
    })
  }
}
</script>

If Javascript is not enabled on the client, then the pager link will remain visible.

tmorehouse avatar May 14 '20 14:05 tmorehouse

@tmorehouse thank you! That works - still seems like a hack around noscript though

dozyio avatar May 14 '20 15:05 dozyio

In fact, noscript is like an HTML native hack in modern web! For today web apps, disabled JS is very uncommon.

alexbruno avatar May 20 '20 17:05 alexbruno

@tmorehouse I tried using your solution, but once I try to nest anything inside <noscript> on SSR phase I get

Property or method "children" is not defined on the instance but referenced during render.

I've created a code sandbox to showcase the issue https://codesandbox.io/s/functional-image-jjnn3?file=/components/NoScript.js:77-85 could you look into it? Thanks

AndrewBogdanovTSS avatar Oct 28 '20 09:10 AndrewBogdanovTSS

@alexbruno <noscript> has plenty of valid use-cases in SSR, ADA and SEO so this feature request is more than valid. your suggestion to use a meta element and redirect to a completely different html-only page is ridiculous. simply because you fail to envision a scenario where it would be useful doesn't make it so. you are being a troll. please stop.

bpossolo avatar Dec 06 '20 04:12 bpossolo

@bpossolo some people are just too active on the topics they have 0 competence in - a side effect of open dev environments :)

AndrewBogdanovTSS avatar Dec 07 '20 10:12 AndrewBogdanovTSS

Ok @bpossolo and @AndrewBogdanovTSS , I think it is surprising that someone needs this markup in modern frontend applications. But there is a lot of mentions about SSR and SEO here... Maybe, for SSR someone would find a use for noscript, but SEO is not the case since years ago. Google Developer docs says that Google Search Engine is able to load, render and crawling from websites built with JS frameworks with dynamic content since 2015. That said, I can't understand where is the SEO needs for noscript. So, @bpossolo and @AndrewBogdanovTSS , I'm sorry if my web dev competence is very limited to contribute, despite my 10 years of experience.

alexbruno avatar Dec 07 '20 18:12 alexbruno

@alexbruno really, I don't see why is it so critical for you to not have it. If you don't see cases that it can be used in your daily tasks - just don't use it, it's that simple. Why you want to forbid it for other who really have a need for it is beyond my understanding

AndrewBogdanovTSS avatar Dec 07 '20 19:12 AndrewBogdanovTSS

@AndrewBogdanovTSS I can't forbid anyone to use anything (maybe my children 🤣 ) ! I'm just trying to say that maybe it is not critical to have it. Maybe there are better ways to develop Vue apps to doesn't need noscript. Like this:

https://developers.google.com/search/blog/2015/10/deprecating-our-ajax-crawling-scheme.html https://developers.google.com/search/blog/2014/05/understanding-web-pages-better

But, I want to apologize again if I can't be able to see your needs.

alexbruno avatar Dec 07 '20 19:12 alexbruno

@alexbruno there are definitely valid use cases. Some sites/apps need to function for users who don't have JS enabled. That's a very tiny fraction of people, but when you're developing government services for example, these need to be usable by every person in the country (within reason), and that includes users who have JS switched off, ether by choice or JS has failed to work in their client for some reason.

For such services, it's best to take a progressive enhancement approach. This is where the noscript tag is invaluable and allows you to place fallback controls for no-JS users that aren't required for users who have JS enabled.

lukenofurther avatar Dec 07 '20 19:12 lukenofurther

My point here is simple, from the point of W3C spec <no-script> is regular tag, which is as valid to use as any other tag, it wasn't deprecated in the spec, it's there for a reason, so it should be rendered as any other valid tag, why should it throw any exceptions? What developers want to use it for - is completely other topic and shouldn't affect base functionality

AndrewBogdanovTSS avatar Dec 08 '20 15:12 AndrewBogdanovTSS

Anyone know if this is fixed in Vue3? Doing some github issue house cleaning and am not working with Vue as much these days...

brophdawg11 avatar May 11 '22 12:05 brophdawg11