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

vue-meta 3.0 #19

Open
Atinux opened this issue Dec 18, 2018 · 28 comments

Comments

@Atinux
Copy link
Member

commented Dec 18, 2018

Introducing @nuxt/vue-app-head to replace vue-meta.

Issues of vue-meta1.0

  • vue-meta is created by Declan Dewet who cannot maintain this project anymore, I took over 2 years ago after helping him so I could maintain it for Nuxt.
  • The Nuxt team has no push access for it We now have!
  • Depends on deepmerge, lodash.isplainobject and object-assign (this will change in a future vue-meta version)
  • 3,6Kb gzip just to support head elements (https://bundlephobia.com/result?p=vue-meta@1.5.6), I think we can do better if we focus on Nuxt integration

vue-meta 2.0

Will be simply a refactor of the actual one with optimisations and bug fixes.

vue-meta 3.0

vue-meta is the default package used by @nuxt/vue-app but it could be disabled by another module (introducing the way to work with hooks with these external modules).

I believe we could introduce a new component: <n-head>

Example (pages/users/[userId].vue):

<template>
  <div>
    <n-head>
      <title>{{ user.name }}</title>
      <meta key="description" name="description" :content="user.description">
    </n-head>
    <pre>{{ user }}</pre>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, params }) {
    const user = await $axios.$get(`https://.../api/users/${params.userId}`)

    return { user }
  }
}
</script>

The point of this component is simply a shortcut to write inside the head key (we should keep this key to let librairies like Vuetify add mixins for it when used with Nuxt).

Here, key is simply the vmid we have in head, I believe by using a functional component, we could achieve easily this behaviour.

This <n-head> should have some props to handle body-attrs and html-attrs, about head-attrs, well, it's all the others non-defined props directly :)

What do you think?

@manniL

This comment has been minimized.

Copy link
Member

commented Dec 18, 2018

As a vue-meta contributor, I can only agree 👍
It'd be great to have a better solution than relying on vue-meta because of the cumbersome process and the space for improvements we'd have by building our own solution.

now that we have control over the repo, let's improve it 💪

Have you thought about going for a completely separate block? Like:

<template>
  <div>
    <pre>{{ user }}</pre>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, params }) {
    const user = await $axios.$get(`https://.../api/users/${params.userId}`)

    return { user }
  }
}
</script>

<n-head>
  <title>{{ user.name }}</title>
  <meta key="description" name="description" :content="user.description">
</n-head>

@manniL manniL added the nuxt label Dec 18, 2018

@Atinux

This comment has been minimized.

Copy link
Member Author

commented Dec 18, 2018

I was thinking also or using a separate block, and by looking at the example, I prefer it :)

But I don't know how to do something like this, I guess it's a plugin for vue-template-compiler?
cc @znck

@manniL

This comment has been minimized.

Copy link
Member

commented Dec 18, 2018

@Atinux We can probably look at https://github.com/kazupon/vue-i18n-loader for an overview ☺️

@znck

This comment has been minimized.

Copy link

commented Dec 18, 2018

A webpack loader is required for handling the custom block. See https://vue-loader.vuejs.org/guide/custom-blocks.html#example

P.S. There's no need to add prefix n-.

<head>
  <title>{{ user.name }}</title>
  <meta key="description" name="description" :content="user.description">
</head>

<template>
  <div>
    <pre>{{ user }}</pre>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, params }) {
    const user = await $axios.$get(`https://.../api/users/${params.userId}`)

    return { user }
  }
}
</script>
@Atinux

This comment has been minimized.

Copy link
Member Author

commented Dec 18, 2018

Thanks @znck for the clarifications :)

The main issue I see here with <head> block, is to not be able to use Vue syntax (v-if, v-bind, etc). As well as the auto completion from the HTML & Vue components definitions...

@pimlie

This comment has been minimized.

Copy link

commented Jan 7, 2019

Maybe we can take some features from headful as well? E.g. to have the possibility to add both title, itemprop=name, og:title as twitter:title tags by only setting a title would be nice. I could imagine this to be configurable by enabling microdata, opengraph and/or twitter props in the config.

@manniL

This comment has been minimized.

Copy link
Member

commented Jan 7, 2019

@pimlie Definitely! We could make meta tag definitions way easier (and also incorporate changes from the meta package of https://github.com/nuxt-community/pwa-module)

@pimlie

This comment has been minimized.

Copy link

commented Jan 10, 2019

Another feature that would be nice to have, would be if this package provided (the tools for) a specific event for tracking meta changes after vue-router navigation. The problem with vue-meta is that the meta changes are asynchronously implemented after navigation. You can listen for the vue-meta.changed event, but that event doesnt always trigger as it only triggers after some meta changes happened. (Also it seems the name changed in vue-meta is slightly wrong, updated would be more accurate as it also fires if you update with exactly the same meta information).

This would solve a small issue I have with nuxt-matomo where I would like to include the document.title in page tracking.

@manniL

This comment has been minimized.

Copy link
Member

commented Jan 27, 2019

@nathanchase

This comment has been minimized.

Copy link

commented Jan 28, 2019

A good resource of things to test against, if someone wanted to implement a test suite for a vue-meta replacement: https://github.com/joshbuchea/HEAD

FWIW, the ease of ability to modify the head and meta tags for SSR is the primary reason I chose Nuxt in the first place as a platform over others.

@Timkor

This comment has been minimized.

Copy link

commented Feb 7, 2019

I think it would be a clean solution to have a separate component for the head just like the loader component.

<template>
    <head>
        <title>{{head.title}}</title>
    </head>
</template>
<script>
    export default {
        props: {
            head: Object // Old head output of page components
        }
    }
</script>

This requires nearly zero overhead. A default head component can be included like the loading indicator. But the head component can also be customized. It can be made that similar input is accepted like vue-meta did. This way it is pretty compatable.

I do not think it's the page component's responsibility to render the head. It should supply some data at most. It is pretty important information regarding SEO. If a lot of people are working in a project, one (less experienced developer) should not easily be able to adjust head information. That's why I think nesting the n-head in the template of page components is a bad idea. It can also be easily overseen in merge conflicts and reviews.

@nathanchase

This comment has been minimized.

Copy link

commented Feb 7, 2019

<head>
   <title v-if="user">{{ user.name }}</title>
   <title v-else>{{ page.name }}</title>
   <meta key="description" name="description" :content="user.description" v-if="user">
</head>

<template>
...
</template>

<script>
...
</script>

I like this syntactically more than any other solution.

@Atinux Atinux changed the title @nuxt/vue-app-head vue-meta 2.0 Feb 19, 2019

@Atinux

This comment has been minimized.

Copy link
Member Author

commented Feb 19, 2019

Thanks for your comments :)

@pimlie already started a refactor of vue-meta: https://github.com/nuxt/vue-meta/tree/next

I believe we don't need @nuxt/vue-app-head but directly introduce breaking changes with vue-meta 2.0 (since it's now on nuxt org) to better fullfill Nuxt needs.

I also suggest that for vue-meta 2.0, metaInfo is renamed to head to avoid any confusions later on.

@pimlie what will be your vision for vue-meta 2 and the breaking changes your already have in mind?

@pimlie

This comment has been minimized.

Copy link

commented Feb 19, 2019

@Atinux We should also probably decide whether we first want to release a refactored version within the vue-meta v1 branch (and maybe add deprecation warnings already if we have decided on those). Because in my mind vue-meta v2 will more be a rewrite then a refactor.

Components

As mentioned above we would like to use components so vue-meta eg doesnt need to render its own html. For this to work we would need to mount two additional root components for resp. head and body-scripts. As the head element doesnt have any possible container elements (except for maybe <noscript>) we would need to mount head as a vue component.
This has the downside that we need to parse existing head content to prevent missing elements on re-render. I have made a proof-of-concept for this and general behaviour here: https://github.com/pimlie/vue-meta/tree/feat-proposal/examples/prop-2.0

Here are some more considerations with regards to these proposed changes and new structure

Considerations

  • As the app and the head will be separate root components, they need to be linked explicitly and cant use eg events or props to communicate 💔 ( = breaking change)
  • The app will still receive a mixin (as it does now) which does metaInfo merging so that head will only receive a plain object. This provide us nice separation of concerns.
  • body-scripts component will be linked to head (the chance of having only body-scripts on your page and no head elements seems negligible to me in real world use-cases). PortalVue could be a solution for this but is quite big. Havent looked into a real implementation for this yet so possible 💔
  • The current metaInfo object is almost a one-to-one copy in structure of html elements, we should add some abstraction to this. See eg the poc where I have a description component which extend a meta component. This way we can abstract the structure (and automatically add a vmid/hid/key) and make it easier to toggle extra features like creating multiple description tags at once (although we probably cant use a description component for this as you cant have nested childs in head and a render fn also needs to return a single root). This might be breaking once we are done with everything, but dont think it will be atm
  • __dangerouslyDisableSanitizers* will be removed, we will probably have something like content and rawContent in-line with Raw HTML in Vue 💔
  • Currently there is one watcher for the full metaInfo object, I would like to break this in to separate watchers for each head setting so updates are as flat as possible. Also it will be easier and quicker to check if there was really a change, but we'd might need additional logic to prevent multiple updates running at the same time (vue-meta currently does this by using rAF)
  • htmlAttrs and bodyAttrs will still need to be processed in a similar way as now (template vars server side and DOM manipulation client side)
  • The head components replaces the existing head which means we need to parse its content to include externally (as in externally from vue-meta) defined head childs. Having to parse html is never fun. That said, client side we dont really as we can just traverse the DOM and server side we can either use jsdom or vue-template-compiler (as in my poc). Also we will probably have at the most a depth of 2 because only noscript can have html elements as childNodes and no head element accept itself as a child. That limits complexity greatly and we should be able to eg ignore this
  • VNodes dont render fully according to w3c spec (eg boolean attributes seems to be wrong). This will be more difficult to fix (if at all) then with our own render implementation

Deep merging

Besides using components I would also like to remove the need for deep-merging. A possible solution for this is to just dont merge ourselves but provide a callback so the user can implement different strategies for which value needs to be set at the time a change occurs. A proof-of-concept for this functionality is here where updateMetaInfo is the callback (ignore the name vuehooks, its based on Vue.observable). But this could be an issue if the value of metaInfo.<key> is an array, eg when you are adding some general meta elements for which no specific component exists. Need to have a look at that later

Status / feedback

Curious to find out what you think about these suggestions! Do you see any caveats with the suggestions above? Please let us know!

@Timkor

This comment has been minimized.

Copy link

commented Feb 23, 2019

@pimlie
Maybe interesting to note that Vue 3.0 will support portals natively.

@Atinux Atinux changed the title vue-meta 2.0 vue-meta 3.0 Mar 28, 2019

@koresar

This comment has been minimized.

Copy link

commented Mar 28, 2019

If I may. The largest problem I had with vue-meta is copy paste. Plenty of copy paste. I had to configure meta tags for every social network (twitter is the most painful) three times in 12 months.

Is this issue a right place to ask a configuration option like this:

twitter: true

and then it just works...

(sorry, I might don't know what I'm talking about)

@manniL

This comment has been minimized.

Copy link
Member

commented Mar 29, 2019

@koresar I know what you mean. 🙈

If you have any ideas to improve, please add them here or in the vue-meta repo ☺️

@cesasol

This comment has been minimized.

Copy link

commented Mar 29, 2019

There is also always a need for structured data from Schema.org, it is really annoying to be writing the complete structure every time you need it, for example a website with blog, cooking recipes, author blog pages and chefs where each one of those has a similar structure but changing slightly in all of them, so I think it would be a good idea to be able to generate labels in the head from components that the developer believes for his project.

Another situation in which these components can be used is when you start having data repetition through different labels, such as having the standard canonical url plus the facebook URL, plus the one from {'@type': 'WebSite'} of Schema.org

All this also would have to use deepmerge and added as when you have several images for og: image or you want to change the description for only one of the nested objects inside the schema ld + json

Currently in my team we do this manually based on a template with a nested Map that searches and adds or modifies based on point syntax.

@Atinux Atinux added nuxt 3 and removed nuxt labels Apr 1, 2019

@theprojectsomething

This comment has been minimized.

Copy link

commented Apr 3, 2019

seconding the need for a ready event (or observable attribute) following a navigation event; to trigger once all defined attributes from all nested views !== undefined. Our use case would be primarily async attributes for SSR.

@theprojectsomething

This comment has been minimized.

Copy link

commented Apr 4, 2019

While I like the idea of a template as mentioned by various contributors above - the format looks good and for the most part says what it is (noted it doesn't include everything contained in the <head>) - I don't see it as an ideal solution.

In my experience the meta tags implemented across pages rarely change (only the values they contain), except perhaps for the addition / exclusion of some schema.org style tags defined by content type, etc. Because of this the format is likely to be overwhelmingly redundant across components, save for a variable changed here or there.

Also important (and as mentioned by @koresar), the same value is more often than not shared across multiple meta tags. So title might be used in <title>, <meta property="og:title"> and <meta itemprop="name">, similarly with description, image, etc. This further adds to redundancy / bloat. One solution might be to allow defining multiple tags per content value/type, however this would be better suited to a variable setup, as per the current implementation - perhaps with a single master <head> template (tho even this seems unnecessary).

Finally, would I be wrong in suggesting that meta data, at least in the head of the document, is generally tied explicitly to the url? Of course there are edge cases, say games running in the title, or applications where state isn't necessarily reflected by url ... I'm sure accessibility could be argued. But in browser and search-engine land, it feels like the url should be the default case for defining distinct meta states...

... Given the above, could meta tags be more closely tied to the router than a component - controlled by stateful data (e.g. Vuex) or events?

@pimlie

This comment has been minimized.

Copy link

commented Apr 23, 2019

@koresar Agreed, re-using titles and descriptions for eg og: stuff is at the top of the wishlist 👍

@cesasol Could you maybe share the implementation you currently use? I am also interested to add this (eg breadcrumbs are also a nice use-case), so having an idea what currently works for you might be helpful :)

@theprojectsomething You might want to take a look at the release candidate for vue-meta 2.0, it adds a refreshOnceOnNavigation option and afterNavigation callback which should already tie meta tags closer to the router while still leaving full reactivity and thus flexibility without restrictions.

Although at the moment vue-meta only supports updating using a component property, that prop is only required on one of all your components. Eg you can easily implement that property only on your root component and have it return stateful data from Vuex or from some flow of events. Although it might be nicer to support that directly in vue-meta (and we will certainly have a look at that), I think your suggestion is already possible for 99% 😄

@pimlie

This comment has been minimized.

Copy link

commented Jun 9, 2019

For vue-meta v3 I think it would be interesting to move to a mono-repository consisting of the following packages (but list is subjected to new insights 😄 )

  • @vue-meta/vue-component-property-merge
    A generic package for deep merging of component properties. Preferably with support for having multiple / configurable merge strategies
  • @vue-meta/vue-app
    The package which contains all components
  • @vue-meta/core
    The core logic glueing everything together
  • @vue-meta/server-plugin
    With ssr support
  • @vue-meta/browser-plugin
    Without ssr support
@Atinux

This comment has been minimized.

Copy link
Member Author

commented Jun 12, 2019

I really like the idea @pimlie

Would like to have @pi0 insights on this too.

@Atinux

This comment has been minimized.

Copy link
Member Author

commented Jun 12, 2019

I will actually rename @vue-meta/vue-app to @vue-meta/components

@pimlie

This comment has been minimized.

Copy link

commented Jun 12, 2019

Actually I am thinking about / trying to come up with a more pluggable configuration (eg looking at webpack for inspiration). I would like to be able to split components per type eg:

  • @vue-meta/components for html spec components
  • @vue-meta/open-graph for og components
  • @vue-meta/twitter for twitter card components

If you load all those pkgs then you should be able to use either one of these:

<meta name="description" content="description">
<html-description value="description">
<og-description value="description">
<twitter-description value="description">
<!-- // and in preparation for Vue v3 -->
<description value="description" html="true" og="true" twitter="true">

But adding an abstraction on top of html components like this is probably only useful when we will also use Vue to render them (at least client side). And we have take this into account before we decide on that: nuxt/vue-meta#394

--edit--
See nuxt/vue-meta#395 for a first implementation of the above but without splitting it into separate packages

@connecteev

This comment has been minimized.

Copy link

commented Jul 27, 2019

@pimlie what is the advantage of splitting it like that? It adds another unnecessary layer of complexity imo. Imagine adding meta tags for a few things and then realizing you need another import to handle twitter, etc...how annoying would that be?

@pimlie

This comment has been minimized.

Copy link

commented Jul 27, 2019

@connecteev fair point, as often the main reason would be separation of concerns. E.g. we could still provide a vue-meta package which already has dependencies on all the possible component packages so you wont have to worry about dependencies. That said, not sure yet if we will really go that way. It might be a bit overkill indeed.

E.g. the main thing I am still struggling with is how to use those tags. There is an initial pr for using a custom-block in vue-loader so you can write a <head> section in your components (see example in my previous comment). But with the current approach these components are first parsed to AST and then into a metaInfo object so we can merge all components together. I am just not really sure this approach provides a real benefit except causing a lot of overhead (mostly for me/us providing this functionality). If we are going to parse those components to a metaInfo object anyway, then why not just let the user write that metaInfo object in the first place? The only reason I can think of is those tags might a bit better readable, but is that really worth all the trouble?

Would appreciate some feedback on that last part ;)

@connecteev

This comment has been minimized.

Copy link

commented Jul 27, 2019

@pimlie

If we are going to parse those components to a metaInfo object anyway, then why not just let the user write that metaInfo object in the first place?

I agree with this. The more we deviate from vue (after all, nuxt is a vue framework), the worse it gets because developers have to now learn 2 formats - one for vue, and one for how nuxt interprets it. So yes, the closer we get to the vue conventions (and push the vue community to adopt better conventions if the current ones dont make sense), the better off everyone will be.
Another (unrelated, but relevant to the point i'm making) example is nuxt/nuxt.js#6102
Just my humble 2c :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants
You can’t perform that action at this time.