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

Closed
Atinux opened this issue Dec 18, 2018 · 41 comments
Closed

vue-meta 3.0 #19

Atinux opened this issue Dec 18, 2018 · 41 comments
Labels

Comments

@Atinux
Copy link
Member

@Atinux Atinux 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
Copy link
Collaborator

@manniL manniL 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
Copy link
Member Author

@Atinux Atinux 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
Copy link
Collaborator

@manniL manniL commented Dec 18, 2018

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

@znck
Copy link

@znck znck 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
Copy link
Member Author

@Atinux Atinux 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
Copy link

@pimlie pimlie 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
Copy link
Collaborator

@manniL manniL 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
Copy link

@pimlie pimlie 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
Copy link
Collaborator

@manniL manniL commented Jan 27, 2019

@nathanchase
Copy link

@nathanchase nathanchase 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
Copy link

@Timkor Timkor 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
Copy link

@nathanchase nathanchase 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
Copy link
Member Author

@Atinux Atinux 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
Copy link

@pimlie pimlie 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
Copy link

@Timkor Timkor 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
Copy link

@koresar koresar 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
Copy link
Collaborator

@manniL manniL 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
Copy link

@cesasol cesasol 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
Copy link

@theprojectsomething theprojectsomething 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
Copy link

@theprojectsomething theprojectsomething 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
Copy link

@pimlie pimlie 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
Copy link

@pimlie pimlie 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
Copy link
Member Author

@Atinux Atinux commented Jun 12, 2019

I really like the idea @pimlie

Would like to have @pi0 insights on this too.

@Atinux
Copy link
Member Author

@Atinux Atinux commented Jun 12, 2019

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

@pimlie
Copy link

@pimlie pimlie 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
Copy link

@connecteev connecteev 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
Copy link

@pimlie pimlie 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
Copy link

@connecteev connecteev 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 :)

@hecktarzuli
Copy link

@hecktarzuli hecktarzuli commented Nov 5, 2019

It would be really nice to have access to the current metas in plugins and not just a ref to the root instance. We've had to resort to a mixin that is used between layouts and page comps and it would really be nice to separate some of it's logic into a plugin. @pimlie :)

@pimlie
Copy link

@pimlie pimlie commented Nov 5, 2019

and not just a ref to the root instance

What exactly do you mean with that? Can you show an example of the api you need?

@hecktarzuli
Copy link

@hecktarzuli hecktarzuli commented Nov 5, 2019

Right now in the context object you get app.head. The problem with this is, if you do anything with it you are changing the root vue instance. If I had a server side plugin that did app.head.meta.push({ name: 'viewport', content: 'width=device-width, initial-scale=1' }). What happens in prod mode, is that tag gets added to the stack on every hit. So after 10 visits to the website, you have 10 viewport tags instead of one.

It would be great if either a) app.head was a clone of the root metas and was created on every request (like it acts in dev mode) or b) there was another property or method passed to the context object that is the current metas for the hit that's getting rendered.

@hecktarzuli
Copy link

@hecktarzuli hecktarzuli commented Dec 17, 2019

@pimlie does that make sense?

@koresar
Copy link

@koresar koresar commented Dec 17, 2019

Hello people.

Replying to this:

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

I have this, probably crazy radical, idea. Some of you might also had it.

Where the idea is coming from.

Recently I had to refactor a configuration-driven backend system. The huge problem with it is that any configuration (or similar DSL) is not scaleable, hard to maintain. If there is a lot of configuration then your system is screwed.

As part of that refactoring I replaced massive 100-1000s config files with 10 lines of JavaScript each. As the result the configuration shrunk from ~2500 lines of code to ~270. Significant improvement. Isn't it? So here is why I came to the following conclusion - the "functional" way is always better than any DSL. Examples:

  • React (functional) JSX is better than jQuery+Handlebars
  • AWS CDK (infrastructure as TypeScript) is better than CloudFormation JSON files.
  • Upcoming Vue 3 composition API is better than Vuex (subjectively).

The idea

The <head> is a HTML tag with a list of tags. Modern days HTML is rendered using the functional (components) approach - React/Vue/etc.

Then why are we trying to render HTML tags with config JSON (essentially a DSL) rather than functions?
We already render <body> with components. Let's render <head> with components too.

I envision <head> to be rendered this way (really inventing the syntax as I type):

A Nuxt.js page:

<template>
    <Whatever/>
</template>

<script>
export default {
    name: "MyPage",
    head() {
       return (
         <MyHead main-title="Example.com website">
            <SeoMeta :twitter="true" :og="true" :facebook="true" content="My example site page" />
          </MyHead>
       );
};
</script>

<style scoped>
</style>

Or, alternatively, to avoid some repetitive code it can be like this

Same Nuxt.js page:

<template>
    <Whatever/>
</template>

<head main-title="Example.com website">
    <SeoMeta :twitter="true" :og="true" :facebook="true" content="My example site page" />
</head>

<script>
export default {
    name: "MyPage"
};
</script>

<style scoped>
</style>

You see where I'm going?

Similar things already exist in React world (I've just googled them).

  1. React portals
  2. react-helmet

@koresar
Copy link

@koresar koresar commented Dec 17, 2019

Ignore my previous message. I've just managed to read the very first message by Sebastien.

Yeah, <n-head> sounds great.

homer

@pimlie
Copy link

@pimlie pimlie commented Dec 17, 2019

@hecktarzuli Thanks for explaining. It makes sense partially I guess, but I think its beyond the scope of vue-meta to facilitate any user pattern. The issue you are describing seems to be caused by Vue's runInNewContext option. In my mind it would make sense that this should be fixed in user land as not every user will need this and cloning an object comes at a cost. Or maybe we could just add some default library method to help you with it (similar to as how you can call Vue.use multiple times but Vue will only install a plugin once normally), feel free to create a PR for that 😉

@koresar Thanks for the nice idea! I've made a (now stale, so you couldnt know unfortunately) proof of concept PR that does this more or less: nuxt/vue-meta#392
The issue seemed to be that normally the Vue app lives in the body, so we cant use the normal Vue-app for parsing the components to html. We could run a separate Vue-app for the head, but instantiating 2 Vue-app's instead of one comes at a measurable performance/startup cost. Vue also knows portals, but at least in Vue2 they dont work for SSR and its not sure yet if/how they will work for Vue3 ssr (afaik).
So thats why the above linked PR first falls back to parsing the vue-loader custom head block into a JSON object and then use that for manually generating the head tags string which then can be injected into the html. Feel free to tinker with it if you know a better implementation 😸

@troxler
Copy link

@troxler troxler commented Dec 7, 2020

Hi! I'm the author of headful (npm) and vue-headful (npm). I only just found this issue now even though headful was already mentioned in this issue. I appreciate that you are considering to implement features from it :)

FWIW, I consider deprecating vue-headful in favour of vue-meta.

@AlexVipond
Copy link

@AlexVipond AlexVipond commented Jan 2, 2021

Inspired by this conversation, I just published the first version of useHead, a composition function you can use to add and update <head> content from inside the setup function in a Vue 3 component. Example use (Vue Router stuff is optional, just included for the sake of example):

import { useHead } from '@baleada/vue-features'
import { useRoute } from 'vue-router'
import { context } from '/path/to/global-store'

export default {
  setup () {
    const route = useRoute()
    useHead({
      title: computed(() => context.article.frontMatter?.title ?? SITE_NAME),
      metas: [
        { 
          property: 'og:title',
          content: computed(() => context.article.frontMatter?.title ?? SITE_NAME)
        },
        { 
          property: 'og:description',
          content: computed(() => context.article.frontMatter?.summary ?? '')
        },
        { 
          property: 'og:image',
          content: computed(() => context.article.frontMatter?.image ?? '')
        },
        { 
          property: 'og:url',
          content: computed(() => route.fullPath),
        },
      ]
    })
  }
}

Under the hood, I had to reimplement a few Vue features:

  • Creating DOM nodes (and removing them onBeforeUnmount
  • Patching DOM nodes' attributes and values

As others in this issue have noted, those actions are easily available when you're working with a Vue template, but they're not accessible from inside composition functions.

I have a feature request open in the Vue 3 repo asking the Vue team to give us lower-level APIs for patching DOM attributes and creating nodes in the Virtual DOM, then relying on Vue to schedule renderer updates efficiently. In the meantime, I wrote a bunch of composition-function-friendly reimplementations of Vue template features like v-bind and v-if. That's what I'm using under the hood of useHead.

This experience has convinced me that composition functions are the right choice for vue-meta's behavior. Once the basic logic is implemented in a composition function, it's very easy to wrap a renderless component around that composition function, and offer the component to anyone who prefers that component-based API. It's also very possible to write a Vue plugin that adds a metaInfo component option, while powering the entire plugin with that same composition function.

If anyone wants to use useHead or any other code I've linked here, let me know! Nothing is typed or documented yet, but I can show additional usage examples to anyone who would like them.

Copy link
Member Author

@Atinux Atinux commented Jan 6, 2021 — with Volta.net

Thank you @AlexVipond

I would love to see a repo even without docs

@AlexVipond
Copy link

@AlexVipond AlexVipond commented Jan 6, 2021

@Atinux Sure thing! Here's the source for the now-published useHead function: https://github.com/baleada/vue-features/blob/main/src/features/useHead.js

Note that only meta and title tags are supported, as that's all I personally needed when I wrote it. Support for link, noscript, etc. wouldn't be difficult to add.

I haven't deeply explored server-side rendering with this composition function, so I'm unsure of how fully it solves SEO in Vue.

@chriscalo
Copy link

@chriscalo chriscalo commented Jan 7, 2021

Related: the API in vue-head feels perfect to me because it so closely resembles plain HTML (much like the original proposal):

<template>
  <Head>
    <title>Hello Vue</title>
    <meta name="description" content="Do you like it?" />
  </Head>
</template>

<script>
import { Head } from '@egoist/vue-head'

export default {
  components: {
    Head,
  },
}
</script>

It also makes simple work of controling the HTML generated by SSR output:

import { createApp, h, Fragment } from 'vue'
import { renderToString } from '@vue/server-renderer'

const app = createApp()
const appHTML = await renderToString(app)
const headHTML = await renderToString(
  h(Fragment, app.config.globalProperties.$head.headTags)
)

const finalHTML = `
<html>

<head>${headHTML}</head>

<body>${appHTML}</body>

</html>
`

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 about the following API so everything feels close to plain HTML?

<template>
  <Html lang="en"/>
  <Head>
    <title>Hello Vue</title>
    <meta name="description" content="Do you like it?" />
  </Head>
  <Body class="foo"/>
</template>

Copy link
Member Author

@Atinux Atinux commented Jan 13, 2021 — with Volta.net

We discussed about something like this for Nuxt 3 with @pi0, we are going to explore and keep this thread updated as soon as we have something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet