Open valentinvieriu opened 7 years ago
@valentinvieriu I using vue-meta https://github.com/declandewet/vue-meta
Can confirm, vue-meta seems to work well.
vue-meta looks lt can do the work. As Vue-hacker-news2.0 should be the main example on how to build an isomorphic app, shouldn't we have this use case built in? Should we try to incorporate vue-meta in the vue-hackernews-2.0?
Though vue-meta looks like a very fine plugin, I don't think we should rely on 3rd-party Vue extensions to in an official core feature demo-app. This would make it seem that
Especially since its README says:
Please note that this project is still in very early alpha development and is not considered to be production ready.
I'd say it's about as necessary as a server-side-rendered app, which is officially supported.
I'd say it's about as necessary as a server-side-rendered app, which is officially supported.
Nessessary enough that we should include a not-production-ready 3rd-party plugin into an official demo that is supposed to demonstrate "best practice"? I think that would be a bad idea.
I agree. I wasn't suggesting that. If it's as necessary as SSR, which many might believe it is, perhaps there should be an official way of doing it?
FYI: I'm happy just using a library like this one.
If it's as necessary as SSR, which many might believe it is, perhaps there should be an official way of doing it?
Good topic for a feature request on the repo @ www.github.comvuejs/vue/issues
vue-meta
this module has some problem on Vue SSR‘s performance. I use 0.4.4 version, and it let memory more and more big, and affect rps. Use it carefully!
I've also come across a few bugs with it that I'm having a hard time working around. It would be good if there was an official library that worked so we don't have everyone reinventing their own libraries or hacks around existing ones.
As this repository is for vue ssr best practice, I ask @yyx990803 and @addyosmani (because they are the first and second contributor to this project) what they suggest about it ?
Hi @daliborgogic
Can you give us more explanation how does it work. Because I understood the change of document title fired by App.vue
but I cant make the changes on og:image
and og.url
.
Thank you in advance.
@ram-you For example in HomeView we have function to set title, description. image, etc in store. On preFetch
store will be updated with this data. When serving head
we replace marker with data from context
#L74
You can test: Example on: https://developers.facebook.com/tools/debug/sharing/ https://cards-dev.twitter.com/validator
@daliborgogic It doesn't work.
If you take, for example the link https://vuejs-ssr-meta-tmflbxpghm.now.sh/contact in Facebook Debug you will see that in <meta property="og:title" content="Home" />
the content still "Home" and not "Contact" .
Furthermore, the only change is done in App.vue
for title is on client rendering not in SSR.
@ram-you og:url fix and Updated example Please use issues
I am using vue-head, even use it to load css file to implement themeable pages.
Hi everyone,
I'm develop some of code for manage the head, but i have some problem. I take the example from hackernew and ssr documentation, and i create a head.js file this looks like this:
const cleanMetas = () => {
/*
TODO:
delete all metas
add metas elementary
return to recreate head from vm.$options
*/
}
const getString = (vm, content) => {
return typeof content === 'function'
? content.call(vm)
: content
}
export const getMeta = (vm, meta, env) => {
if(typeof meta !== 'object')
return
if(env){
return Object.keys(meta)
.map(value => {
return Object.keys(meta[value])
.map(key => `${key}="${getString(vm, meta[value][key])}"`)
.join(" ");
})
.map(value => ` <meta ${value} >`)
.join("\n");
} else {
return meta
}
}
const serverHeadMixin = {
created () {
const { head } = this.$options
if(head){
const { title } = head
if(title){
this.$ssrContext.title = `${getString(this, title)} :: Vue SSR`
}
const { meta } = head
if(meta)
this.$ssrContext.meta = `\n${getMeta(this, meta, true)}`
}
}
}
const clientHeadMixin = {
mounted () {
cleanMetas()
const { head } = this.$options
if(head){
const { title } = head
if(title){
document.title = `${getString(this, title)} :: Vue SSR`
}
const { meta } = head
/*
TODO:
recreate new metas set in this.$options.head.meta
*/
}
}
}
export default process.env.VUE_ENV === 'server'
? serverHeadMixin
: clientHeadMixin
and i add like a mixin in app.js like this:
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
import { sync } from 'vuex-router-sync'
import Firebase from './firebase/plugin'
import headMixin from './util/head'
Vue.mixin(headMixin)
export function createApp (context) {
const router = createRouter()
const store = createStore()
/*
* Add property $firebase tu Vue instance
*/
Vue.use(Firebase, store.state.config)
// sync so that route state is available as part of the store
sync(store, router)
const app = new Vue({
router,
store,
render: h => h(App)
})
return { app, router, store }
}
One of my component looks like that:
<template>
<div class="home">
<h3>Sección del Home {{ data }}</h3>
<p>Area de trabajo inicial</p>
<pre>{{ item }}</pre>
</div>
</template>
<script>
export default {
head: {
title() {return `${this.name} con `} ,
meta: [
{ name: 'description', content() { return `El perfil de ${this.name}`} },
{ name: 'keywords', content() { return `${this.name} ${this.lastname} ${this.email}` } },
{ name:"twitter:card", content:"summary"},
{ name:"twitter:site", content:"@jqEmprendedorVE"},
{ name:"twitter:creator", content:"@jqEmprendedorVE"},
{ name:"twitter:url", content:"https://vue-firebase-ssr.firebaseapp.com"},
{ name:"twitter:title", content() {return `${this.name} creando Vuejs SSR + Firebase`}},
{ name:"twitter:description", content:"Modelo de Vuejs SSR con Firebase Cloud Function + Hosting"},
{ name:"twitter:image", content:"https://www.filepicker.io/api/file/nS7a8itSTcaAsyct6rVp"}
]
},
asyncData ({ store, route }) {
// return the Promise from the action
return store.dispatch('fetchItem', 1)
},
computed: {
// display the item from store state.
item () {
return this.$store.state.items[1]
},
name () {
return this.$store.state.items[1].nombre
},
lastname () {
return this.$store.state.items[1].apellido
},
email () {
return this.$store.state.items[1].correo
}
},
data() {
return {
data: ':: SSR'
}
},
created() {
this.$firebase.db().ref('data').once('value', snapshot=>{
// console.log(snapshot.val())
})
}
}
</script>
My problem is the following, when rendering on the client side, and changing the view to the other, the new route maintains the metatag of the previous view when doing CSR, I have no problem to load on the SSR, but on the client It does not look complete yet.
Up to now I can load dynamics:
title, CSR and SSR meta only SSR
Ready,
I finished...
const cleanMetas = () => {
return new Promise ((resolve, reject)=>{
const items = document.head.querySelectorAll('meta')
for(const i in items) {
if(typeof items[i]==='object' && ['viewport'].findIndex(val=>val===items[i].name)!=0 && items[i].name!=='')
document.head.removeChild(items[i])
}
resolve()
})
}
const createMeta = (vm, name, ...attr) => {
const meta = document.createElement('meta')
meta.setAttribute(name[0], name[1])
for(const i in attr){
const at = attr[i]
for(const k in at) {
meta.setAttribute(at[k][0], getString(vm, at[k][1]))
}
}
document.head.appendChild(meta);
}
const getString = (vm, content) => {
return typeof content === 'function'
? content.call(vm)
: content
}
export const getMeta = (vm, meta, env) => {
if(typeof meta !== 'object')
return
if(env){
return Object.keys(meta)
.map(value => {
return Object.keys(meta[value])
.map(key => `${key}="${getString(vm, meta[value][key])}"`)
.join(" ");
})
.map(value => ` <meta ${value} >`)
.join("\n");
} else {
return meta
}
}
const serverHeadMixin = {
created () {
const { head } = this.$options
if(head){
const { title } = head
if(title){
this.$ssrContext.title = `${getString(this, title)} :: Vue SSR`
}
const { meta } = head
if(meta)
this.$ssrContext.meta = `\n${getMeta(this, meta, true)}`
}
}
}
const clientHeadMixin = {
mounted () {
const vm = this
const { head } = this.$options
if(head){
const { title } = head
if(title){
document.title = `${getString(this, title)} :: Vue SSR`
}
cleanMetas().then(()=>{
const { meta } = head
if(meta){
for(const nm in meta) {
const name = Object.entries(meta[nm])[0]
const attr = Object.entries(meta[nm]).splice(1,Object.entries(meta[nm]).length)
createMeta(vm, name, attr)
}
}
})
}
}
}
export default process.env.VUE_ENV === 'server'
? serverHeadMixin
: clientHeadMixin
...
import headMixin from './util/head'
Vue.mixin(headMixin)
...
<template>
<div class="home">
<h3>Sección del Home {{ data }}</h3>
<p>Area de trabajo inicial</p>
<pre>{{ item }}</pre>
</div>
</template>
<script>
export default {
head: {
title() {return `${this.name} con `} ,
meta: [
{ name: 'description', content() { return `El perfil de ${this.name}`} },
]
},
asyncData ({ store, route }) {
// return the Promise from the action
return store.dispatch('fetchItem', 1)
},
computed: {
// display the item from store state.
item () {
return this.$store.state.items[1]
},
name () {
return this.$store.state.items[1].nombre
},
lastname () {
return this.$store.state.items[1].apellido
},
email () {
return this.$store.state.items[1].correo
}
},
data() {
return {
data: ':: SSR'
}
},
created() {
this.$firebase.db().ref('data').once('value', snapshot=>{
// console.log(snapshot.val())
})
}
}
</script>
The content data can be string or a function, combine function with computed and asynData for a one best experience in pre-fetching
@jqEmprendedorVE I tried your approach and it does append the meta's in the head, (I see it in inspector) but once I try to share something it does not seem to work. Like there is no meta tags there. Also site inspectors like (https://opengraphcheck.com/) do not seem to pick it up as well
Any ideas why? And how to fix it?
Hello @sutra, I know it's an old question. But can you send me a little example of vue-head code to set css file dynamically, please ? I tried to use vue-head but all I get is the
from index.html (set in my app).Here is my App.vue where I use vue-head :
`
` This vue fails when app is loaded and I can see in source that the head comes from index.html. What am I doing wrong ?
Here is App.vue :
[
](url)
Thanks a lot in advance.
@Eric-Bryan on Sep 27, 2017, I have replaced vue-head
with vue-meta
in my project.
@desicne You have to fulfill your code with og tags:
head: {
title() {
return "Hello world"
},
meta: [
{ name: "description", content: "summary" },
{ name: "keywords", content: "summary" },
{ name: "twitter:card", content: "summary" },
{ name: "twitter:site", content: "@jqEmprendedorVE" },
{ name: "twitter:creator", content: "@jqEmprendedorVE" },
{ name: "twitter:url", content: "https://vue-firebase-ssr.firebaseapp.com" },
{ name: "twitter:description", content: "Modelo de Vuejs SSR con Firebase Cloud Function + Hosting" },
{ name: "twitter:image", content: "https://www.filepicker.io/api/file/nS7a8itSTcaAsyct6rVp" },
{ property: "og:title", content: "summary" },
{ property: "og:type", content: "summary" },
{ property: "og:url", content: "summary" },
{ property: "og:image", content: "summary" },
{ property: "twitter:card", content: "summary" }
]
}
Working for me.
Hi @jqEmprendedorVE , your code works well! Thanks!
But one question, the added meta
will not remove itself, any idea?
all this trouble just to add some meta tags...
plain javascript worked for me. in the index.html file, at the bottom of the body:
<script>
document.title = "whatever";
var metaTag=document.createElement('meta');
metaTag.name = "viewport"
metaTag.content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
document.getElementsByTagName('head')[0].appendChild(metaTag);
</script>
this is definitely something the developers of Vue need to improve in future updates.
Let's say that this needs to be SEO friendly. What would be the best practice of making the
section editable but keep also the streaming approach