Change Meta Title and Description using Vue.js - javascript

Is it possible to change anything higher than the body tag in Vue.Js? The contents for both these elements is currently stored in the JSON file that is attached to an element further down the DOM tree.
I need to try and inject a meta title and description that can be crawled by Google (ie. It injects, then renders before it gets crawled) and understand the issues with accessing the body element and higher up the DOM tree, as the current Vue JSON is injected using the App ID on a DIV lower down.
I have previously used some jQuery code to address this issue on a Square Space template in some previous work
jQuery('meta[name=description]').attr('content', 'Enter Meta Description Here');
PAGE HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="{{items[0][0].meta-desc}}">
<meta name="author" content="">
<title>{{items[0][0].meta-title}}</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<!-- Vue.js CDN -->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!-- Page List -->
<div class="container text-center mt-5" id="app">
<h1 class="display-4">Vue Page Output:</h1>
<h2>{{items[0][0].page1}}</h2>
</div>
<div class="container text-center mt-5">
<h3>Other Pages</h3>
Products
Contact Us
</div>
<!-- /.container -->
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
items: []
},
created: function () {
fetch('test.json')
.then(resp => resp.json())
.then(items => {
this.items = items
})
}
});
</script>
</body>
</html>
JSON
[
[
{
"page1": "Company Name",
"meta-title": "Acme Corp"
"meta-desc": "Welcome to Acme Corp"
}
],
[
{
"products": "Product List"
}
],
[
{
"contactus": "Contact Us at Acme Corp"
}
]
Here is the code in action, the incoming JSON file comes in a fixed array format with the meta details alongside the body elements. Making this a bit more tricky.
https://arraydemo.netlify.com/

Since what you want to change is outside the area controlled by Vue, you just use ordinary DOM manipulation. It would be something like
created() {
fetch('test.json')
.then(resp => resp.json())
.then(items => {
this.items = items;
const descEl = document.querySelector('head meta[name="description"]');
const titleEl = document.querySelector('head title');
descEl.setAttribute('content', items[0]['meta-desc']);
titleEl.textContent = items[0]['meta-title'];
})
}

If you are using Vue Router, I believe that cleaner solution is using beforeEach hook:
const routes = [{ path: '/list', component: List, meta: {title:'List'} }]
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next()
})
But it allows you set only static titles.
However, if you are looking for some SEO optimizations, Nuxt will probably solve most of your problems

Vue meta is an NPM package for meta-data management:
https://vue-meta.nuxtjs.org/
Example of how I use it in a vue page component:
export default {
name: "Contact",
metaInfo: function() {
return {
title: "My page meta title",
meta: [
{ name: 'description', content: "My page meta description" }
]
}
}
If you use Vue Router(that's what I'm doing) you can set Vue meta here so all your page can use it:
import Vue from 'vue'
import Router from 'vue-router'
import VueMeta from 'vue-meta'
Vue.use(Router)
Vue.use(VueMeta)
export default new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
name: 'Home',
component: Home
},

If you want to change the title it is easy to change in vuejs.
In router.js file while creating route you can do something like this.
{
path:"/productdetail",
name:"productdetail",
component:ProductDetail,
meta: {
title : localStorage.getItem("title"),
}
}
router.beforeEach((toRoute,fromRoute,next) => {
window.document.title = toRoute.meta.title;
next();
})
use of localStorage will help you to change title dynamically.
unfortunately, meta description is not changing with the same method.

Related

How do I display two components with vue.js?

this is my first post on here! Just need some small help with a problem. Thanks!
I just started learning vue.js and I'm trying to get my second components template to display on the page. For some reason, vue isn't rendering my second component, only the first. I'm thinking something is wrong within the index file with <education :data='data' />. Do I need to create a different <div id='app'> and put the second component within it?
app.js:
// Fetch handler
function fetchData(url) {
return fetch(url)
.then(checkStatus)
.then(res => res.json())
.catch(error => console.log('Error retrieving data', error))
}
// Response error handler
function checkStatus(res) {
if(res.ok) {
return Promise.resolve(res);
} else {
return Promise.reject(new Error(res.statusText));
}
}
(() => {
// Define components.
Vue.component('contact-info', {
props: ['data'],
template: `
<div>
<img v-bind:src='data.photo'>
<h1>{{ data.name }}</h1>
<p>{{ data.email }}</p><p>{{ data.phone }}</p>
<p v-if='data'>{{ data.links[0] }}</p><p v-if='data'>{{ data.links[1] }}</p>
</div>
`
});
Vue.component('education', {
props: ['data'],
template: `
<div>
<h3>Education</h3>
<div>
<h2></h2>
<p></p>
<p></p>
<div>
<div>
`
});
// Instantiate the app.
const app = new Vue({
el: '#app',
data() {
return {
data: ''
}
},
mounted() {
fetchData('https://wip.journeygroup.com/intern-api/joshbryant.json')
.then(jsonData => (this.data = jsonData))
}
});
})();
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Resume • Josh Bryant</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght#400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/main.css">
</head>
<body>
<div id="app">
<contact-info :data='data' />
<education :data='data' />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script src="./app/main.js"></script>
</body>
</html>
Your post shows app.js though the HTML looks for main.js, but I'll assume that's just a posting error since you've gotten some of it working.
The reason it doesn't work is because custom HTML components require a closing tag and cannot be self-closing. If you were using Vue CLI this would work anyway because the compiler would fix it, but not in a CDN implementation of Vue. The first component will work but everything that follows will be invalid HTML since the first component isn't closed properly.
This will work:
<div id="app">
<contact-info :data="data"></contact-info>
<education :data="data"></education>
</div>
A couple of other suggestions
As #tao mentioned in comments, it's confusing to call your variable data in the root component, and similarly to call your props data. These won't cause a problem but probably best to rename them all for clarity (so as not to be confused with the component's data option.)
There's an invalid bullet character in the HTML <title> tag which you can replace with &bullet;
It's best to use double quotes for your HTML attributes (the props use single quotes in your code)

Use a vanilla JavaScript package in React app

I am aiming to use a Vanilla JavaScript package in a more sophisticated React app to build additional logic around the JavaScript package.
The JavaScript library is LabelStudio and docs can be found here: https://github.com/heartexlabs/label-studio-frontend
However, when I try to import the LabelStudio I get an error saying Module not found: Can't resolve 'label-studio' , as described here https://github.com/heartexlabs/label-studio-frontend/issues/55
Since my understanding of frontend code is limited, I am not sure whether this is something the developers did not expected users to do and just wanted them to use the entire library and customized instead of using the library as a component. My idea was to use the library as in the vanilla javascript example here:
<!-- Include Label Studio stylesheet -->
<link href="https://unpkg.com/label-studio#0.7.1/build/static/css/main.0a1ce8ac.css" rel="stylesheet">
<!-- Create the Label Studio container -->
<div id="label-studio"></div>
<!-- Include the Label Studio library -->
<script src="https://unpkg.com/label-studio#0.7.1/build/static/js/main.3ee35cc9.js"></script>
<!-- Initialize Label Studio -->
<script>
var labelStudio = new LabelStudio('label-studio', {
config: `
<View>
<Image name="img" value="$image"></Image>
<RectangleLabels name="tag" toName="img">
<Label value="Hello"></Label>
<Label value="World"></Label>
</RectangleLabels>
</View>
`,
interfaces: [
"panel",
"update",
"controls",
"side-column",
"completions:menu",
"completions:add-new",
"completions:delete",
"predictions:menu",
],
user: {
pk: 1,
firstName: "James",
lastName: "Dean"
},
task: {
completions: [],
predictions: [],
id: 1,
data: {
image: "https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg"
}
},
onLabelStudioLoad: function(LS) {
var c = LS.completionStore.addCompletion({
userGenerate: true
});
LS.completionStore.selectCompletion(c.id);
}
});
</script>
How can I make use of the above code in a React Component to facilitate dynamic data loading and use of state to customize the functions?
I don't have a solution making the npm module label-studio to work. I tried importing the dist file instead, but it errors
Expected an assignment or function call and instead saw an expression
So here's a workaround until the maintainers address this.
Copy the JS file from build/static/js, then place it in a script in the public folder on index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="%Your_Path_To_Label-Studio%/main.js"></script>
</body>
</html>
The script file defines a global function variable, so you can access it in React by using the window object. The useEffect hook is to make sure the initialization is only run once.
import React, { useEffect, useRef } from "react";
function App() {
const LabelStudio = window.LabelStudio; // label-studio script stores the api globally, similar to how jQuery does
const myLabelStudioRef = useRef(null); // store it and then pass it other components
useEffect(() => {
myLabelStudioRef.current = new LabelStudio("label-studio", {
config: `
<View>
<Image name="img" value="$image"></Image>
<RectangleLabels name="tag" toName="img">
<Label value="Hello"></Label>
<Label value="World"></Label>
</RectangleLabels>
</View>
`,
interfaces: [
"panel",
"update",
"controls",
"side-column",
"completions:menu",
"completions:add-new",
"completions:delete",
"predictions:menu",
],
user: {
pk: 1,
firstName: "James",
lastName: "Dean",
},
task: {
completions: [],
predictions: [],
id: 1,
data: {
image:
"https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg",
},
},
onLabelStudioLoad: function (LS) {
var c = LS.completionStore.addCompletion({
userGenerate: true,
});
LS.completionStore.selectCompletion(c.id);
},
});
}, []);
return (
<div className="App">
{/* Use Label Studio container */}
<div id="label-studio"></div>
</div>
);
}
export default App;
As far as storing the new instance of LabelStudio, there's many ways to go about it. You can store it as variable on the root component using either useState or useRef hooks and then pass it to child components. If you want to avoid manually passing variable down the component tree, then you need a state manager such as React Context or Redux.
This is the basic setup using LabelStudio as a module in a React component.
yarn add #heartexlabs/label-studio
import LabelStudio from '#heartexlabs/label-studio'
import { useEffect } from 'react'
import '#heartexlabs/label-studio/build/static/css/main.css'
const ReactLabelStudio = () => {
useEffect(() => {
new LabelStudio('label-studio', {
config: {...},
interfaces: [...],
user: {...},
task: {...},
onLabelStudioLoad: function(LS) {
const c = LS.annotationStore.addAnnotation({
userGenerate: true,
});
LS.annotationStore.selectAnnotation(c.id);
}
})
}, [])
return <div id="label-studio" />
}
export default ReactLabelStudio

How to dynamically load components in routes

I'm a Vue newbie and I'm experimenting with vue-router and dynamic loading of components without using any additional libraries (so no webpack or similar).
I have created an index page and set up a router. When I first load the page I can see that subpage.js has not been loaded, and when I click the <router-link> I can see that the subpage.js file is loaded. However, the URL does not change, nor does the component appear.
This is what I have so far:
index.html
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<router-link to="/subpage">To subpage</router-link>
<router-view></router-view>
</div>
<script src="main.js"></script>
</body>
</html>
main.js
const router = new VueRouter({
routes: [
{ path: '/subpage', component: () => import('./subpage.js') }
]
})
const app = new Vue({
router
}).$mount('#app');
subpage.js
export default {
name: 'SubPage',
template: '<div>SubPage path: {{msg}}</div>'
data: function() {
return {
msg: this.$route.path
}
}
};
So the question boils down to: How can I dynamically load a component?
How can I dynamically load a component?
Try this:
App.vue
<template>
<div id="app">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<hr/>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
components: {}
};
</script>
main.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
Vue.use(VueRouter);
Vue.config.productionTip = false;
const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');
const router = new VueRouter({
mode: 'history',
routes:[
{path:'/', component: Home},
{path:'/about',component: About}
]
})
new Vue({
router,
render: h => h(App)
}).$mount('#app');
Home.vue
<template>
<div>
<h2>Home</h2>
</div>
</template>
<script>
export default {
name: 'Home'
};
</script>
About.vue
<template>
<div>
<h2>About</h2>
</div>
</template>
<script>
export default {
name: 'About'
};
</script>
This way, the component Home will be automatically loaded.
This is the demo: https://codesandbox.io/s/48qw3x8mvx
I share your wishes for "as lean as possible" codebase and therefore made this simple example code below (also accessible at https://codesandbox.io/embed/64j8pypr4k).
I am no Vue poweruser either, but when researching I have thought about three possibilities;
dynamic imports,
requirejs,
old school JS generated <script src /> include.
It looks like the last is the easiest and takes least effort too :D Probably not best practice and probably obsolete soon (at least affter dynamic import support).
NB: This example is friendly to more recent browsers (with native Promises, Fetch, Arrow functions...). So - use latest Chrome or Firefox to test :) Supporting older browsers may be done with some polyfills and refactoring etc. But it will add a lot to codebase...
So - dynamically loading components, on demand (and not included before):
index.html
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vue lazyload test</title>
<style>
html,body{
margin:5px;
padding:0;
font-family: sans-serif;
}
nav a{
display:block;
margin: 5px 0;
}
nav, main{
border:1px solid;
padding: 10px;
margin-top:5px;
}
.output {
font-weight: bold;
}
</style>
</head>
<body>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/simple">Simple component</router-link>
<router-link to="/complex">Not sooo simple component</router-link>
</nav>
<main>
<router-view></router-view>
</main>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.0.1/vue-router.min.js"></script>
<script>
function loadComponent(componentName, path) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = path;
script.async = true;
script.onload = function() {
var component = Vue.component(componentName);
if (component) {
resolve(component);
} else {
reject();
}
};
script.onerror = reject;
document.body.appendChild(script);
});
}
var router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: {
template: '<div>Home page</div>'
},
},
{
path: '/simple',
component: function(resolve, reject) {
loadComponent('simple', 'simple.js').then(resolve, reject);
}
},
{ path: '/complex', component: function(resolve, reject) { loadComponent('complex', 'complex.js').then(resolve, reject); }
}
]
});
var app = new Vue({
el: '#app',
router: router,
});
</script>
</body>
</html>
simple.js:
Vue.component("simple", {
template: "<div>Simple template page loaded from external file</div>"
});
complex.js:
Vue.component("complex", {
template:
"<div class='complex-content'>Complex template page loaded from external file<br /><br />SubPage path: <i>{{path}}</i><hr /><b>Externally loaded data with some delay:</b><br /> <span class='output' v-html='msg'></span></div>",
data: function() {
return {
path: this.$route.path,
msg: '<p style="color: yellow;">Please wait...</p>'
};
},
methods: {
fetchData() {
var that = this;
setTimeout(() => {
/* a bit delay to simulate latency :D */
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(json => {
console.log(json);
that.msg =
'<p style="color: green;">' + JSON.stringify(json) + "</p>";
})
.catch(error => {
console.log(error);
that.msg =
'<p style="color: red;">Error fetching: ' + error + "</p>";
});
}, 2000);
}
},
created() {
this.fetchData();
}
});
As you can see - function loadComponent() does the "magic" thing of loading components here.
So it works, but it is probably not the best solution, with regards to the (at least) following:
inserting tags with JS can be treated as a security problem
in the near future,
performance - synchronously loading files block the thread (this can
be a major no-no later in app's life),
I did not test caching etc. Can be a real problem in production,
You loose the beauty of (Vue) components - like scoped css, html and
JS that can be automatically bundled with Webpack or something,
You loose the Babel compilation/transpilation,
Hot Module Replacement (and state persistance etc) - gone, I believe,
I probably forgot about other problems that are obvious for
senior-seniors :D
Hope I helped you though :D
I wanted to see how usefull are "new" dynamic imports today (https://developers.google.com/web/updates/2017/11/dynamic-import), so I did some experiments with it. They do make async imports way easier and below is my example code (no Webpack / Babel / just pure Chrome-friendly JS).
I will keep my old answer (How to dynamically load components in routes) for potential reference - loading scripts that way works in more browsers than dynamic imports do (https://caniuse.com/#feat=es6-module-dynamic-import).
So at the end I noticed that you were actually very, very, very close with your work - it was actually just a syntax error when exporting imported JS module (missing comma).
Example below was also working for me (unfortunately Codesandbox's (es)lint does not allow the syntax, but I have checked it locally and it worked (in Chrome, even Firefox does not like the syntax yet: (SyntaxError: the import keyword may only appear in a module) ));
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<router-link to="/temp">To temp</router-link>
<router-link to="/module">To module</router-link>
<router-view></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="main.js"></script>
</body>
</html>
main.js:
'use strict';
const LazyRouteComponent = {
template: '<div>Route:{{msg}}</div>',
data: function() {
return {
msg: this.$route.path
}
}
}
const router = new VueRouter({
routes: [
{
path: '/temp',
component: {
template: '<div>Hello temp: {{msg}}</div>',
data: function() {
return {
msg: this.$route.path
}
}
}
},
{ path: '/module', name: 'module', component: () => import('./module.js')},
{ path: '*', component: LazyRouteComponent }
]
})
const app = new Vue({
router
}).$mount('#app');
and the key difference, module.js:
export default {
name: 'module',
template: '<div>Test Module loaded ASYNC this.$route.path:{{msg}}</div>',
data: function () {
return {
msg: this.$route.path
}
},
mounted: function () {
this.$nextTick(function () {
console.log("entire view has been rendered after module loaded Async");
})
}
}
So - allmost exactly like your code - but with all the commas;
subpage.js
export default {
name: 'SubPage',
template: '<div>SubPage path: {{msg}}</div>',
data: function() {
return {
msg: this.$route.path
}
}
};
So - your code works (i tested it by copy pasting) - you were actually just missing a comma after template: '<div>SubPage path: {{msg}}</div>'.
Nevertheless this only seems to work in:
Chrome >= v63
Chrome for Android >= v69
Safari >= v11.1
IOS Safari >= v11.2
(https://caniuse.com/#feat=es6-module-dynamic-import)...

Vue.js : "TypeError: Cannot set property props of #<Object> which has only a getter"

I am trying to instanciate a Vue component and I am getting the error :
[Vue warn]: Error in render: "TypeError: Cannot set property props of
#<Object> which has only a getter"
(found in <Root>)
I am also using the library vuedraggable but I presume that the problem is more a Vue problem than a vuedraggable one. Below is my code.
Here is draggable-list.vue
<template src="./draggable-list-component.html"></template>
<script src="./draggable-list.js"></script>
draggable-list.js
const draggable = require("vuedraggable");
module.exports = {
name: "draggable-list",
components: {
draggable
},
// properties which has been passed from the parent vue to the component
props: ["title", "elements"],
data() {
return {
isDragging: false,
};
},
methods: {
test() {
console.log("blou");
}
}
};
draggable-list-component.html :
<div id="draggable-list">
<draggable element="ul"
:list="elements">
<!-- TODO -->
</draggable>
</div>
My main.js calls then another js file :
require("./components/manager");
In the manager I instanciate my Vue instance :
const Vue = require("vue/dist/vue.common");
const draggableList = require("./draggable-list.vue");
let triggerLibrary;
triggerLibrary = new Vue({
el: "#draggable-list",
template: "<draggableList :title='title' :elements='triggerElements'
/>",
components: {draggableList},
data: {
title: "Trigger library",
triggerElements: [{name:"Trigger name", description:"Quick trigger
description"}]
}
});
And I am using it in my index.html like this :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>planner</title>
</head>
<body>
<div id="draggable-list">
<draggable-list></draggable-list>
</div>
</body>
Does anyone see what's wrong here?
As you are using CommonJS modules, try to require your Vue component that way :
const draggableList = require("./draggable-list.vue").default;

How can I bind the html <title> content in vuejs?

I'm trying a demo on vuejs. Now I want the html title to bind a vm field.
The below is what I tried:
index.html
<!DOCTYPE html>
<html id="html">
<head>
<title>{{ hello }}</title>
<script src="lib/requirejs/require.min.js" data-main="app"></script>
</head>
<body>
{{ hello }}
<input v-model="hello" title="hello" />
</body>
</html>
app.js
define([
'jquery', 'vue'
], function ($, Vue) {
var vm = new Vue({
el: 'html',
data: {
hello: 'Hello world'
}
});
});
But the title seemed not bounded, how to make it work?
There are essentially two ways to solve it.
Use an existing Package
For example, vue-meta:
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
metaInfo: {
// if no subcomponents specify a metaInfo.title, this title will be used
title: 'Default Title',
// all titles will be injected into this template
titleTemplate: '%s | My Awesome Webapp'
}
}
</script>
Create your own Component
Create a vue file containing:
<script>
export default {
name: 'vue-title',
props: ['title'],
watch: {
title: {
immediate: true,
handler() {
document.title = this.title;
}
}
},
render () {
},
}
</script>
Register the component using
import titleComponent from './title.component.vue';
Vue.component('vue-title', titleComponent);
Then you can use it in your templates, e.g.
<vue-title title="Static Title"></vue-title>
<vue-title :title="dynamic.something + ' - Static'"></vue-title>
You can do it with 1 line in the App.vue file, like this:
<script>
export default {
name: 'app',
created () {
document.title = "Look Ma!";
}
}
</script>
Or change the <title> tag content in public/index.html
<!DOCTYPE html>
<html>
<head>
<title>Look Ma!</title> <!- ------ Here ->
</head>
...
This answer is for vue 1.x
using requirejs.
define([
'https://cdn.jsdelivr.net/vue/latest/vue.js'
], function(Vue) {
var vm = new Vue({
el: 'html',
data: {
hello: 'Hello world'
}
});
});
<!DOCTYPE html>
<html id="html">
<head>
<title>{{ hello }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.js" data-main="app"></script>
</head>
<body>
{{ hello }}
<input v-model="hello" title="hello" />
</body>
</html>
you can do it like this using the ready function to set the initial value and watch to update when the data changes.
<html>
<head>
<title>Replace Me</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input v-model="title">
</div>
<script>
new Vue({
el: '#app',
ready: function () {
document.title = this.title
},
data: {
title: 'My Title'
},
watch: {
title: function (val, old) {
document.title = val
}
}
})
</script>
</body>
</html>
also i tried this based on your original code and it works
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input v-model="title">
</div>
<script>
new Vue({
el: 'html',
data: {
title: 'My Title'
}
})
</script>
</body>
</html>
Just to chime in here. I have read that VueJS wants nothing to do with the meta stuff so I would do such things outside of the "VueJS" realm.
Basically make a plain vanilla js service like below. Here you could add all the functions to handle the meta data stuff such as the Open Graph data.
meta.js
export setTitle(title) {
document.title = title
}
Now we can import the service in main and then provide it to any component in the app who wants it. I could even use my meta service in other projects too which use different frameworks like React or Angular. Portability is super cool!
main.js
import meta from './meta'
new Vue({
router,
render: h => h(App),
provide: {
meta: meta
}
}).$mount('#app')
Here the component injects the meta service it wants to use.
someView.vue
export default {
name: 'someView',
inject: ['meta'],
data: function() {
returns {
title: 'Cool title'
}
},
created: function() {
this.meta.setTitle(this.title);
}
}
This way the meta service is decoupled from the app because different parent components can provide different versions of the meta service. Now you can implement various strategies to see which one is right for you or even different strategies per component.
Basically the inject walks up the component hierarchy and takes the meta service from the first parent who provides it. As long as the meta service follows a proper interface, you're golden.
Decoupling with DI is super cool 😃
Title and meta tags can be edited and updated asynchronously.
You can use state management, create a store for SEO using vuex and update each part accordingly.
Or you can update the element by yourself easily
created: function() {
ajax().then(function(data){
document.title = data.title
document.head.querySelector('meta[name=description]').content = data.description
})
}
If you are using Vuex and want <title> to be part of your application state, then:
create a pageTitle state variable in Vuex
map the state to the template using mapState()
watch it in template, probably add immediate: true to trigger the watcher right away
in watcher, document.title = pageTitle
This will allow you to manage title with Vuex and keep them in sync. I found it useful for SPAs.
By doing this you don't have to mess with your original HTML template, as most of the time Vue root template resides inside <body>.
This is for Vue 2.x.
router.beforeEach((to, from, next) => {
let mohican = to.path; if (mohican == '/') mohican = 'Home'
document.title = mohican.replace('/','');
next();
return;
});
I have an application toolbar component which is common for all pages of my SPA website and is nested in App.vue. In every page I update my common toolbar title in the created hook of the page using Vuex store:
//in every page.vue
created() {
this.$store.commit('toolBar', { pageTitle: this.pageTitle, ... })
},
To automatically update the website title (along with the toolbar title) I use this mutation in the store:
//store.js
toolBar(state,val){
document.title = val.pageTitle
state.toolBar = val
},
Similarly, I use the same mechanism to update e.g. SEO metadata
just pass
:title="data.name"

Categories