Reactive meta, link, and script tags
Change meta tags on the fly in Meteor.js apps via flow-router-extra API. This package manages meta tags, script and link via simple router object definitions.
Features:
- 👷♂️ 100% tests coverage;
- 🎛 Per route, per group, and default (all routes)
metatags; - 🎛 Per route, per group, and default (all routes)
scripts; - 🎛 Per route, per group, and default (all routes)
link, like CSS files.
Various ways to set meta, script and link tags, ordered by priority:
FlowRouter.route()[overrides all below]FlowRouter.group()FlowRouter.globals- Head template
<meta/>,<link/>,<script/>tags [superseded by any above]
ToC
Install
meteor add ostrio:flow-router-meta
[!NOTE] This package implies
ostrio:flow-router-titlepackage.
Demos
ES6 Import
1import { FlowRouterMeta } from 'meteor/ostrio:flow-router-meta'; 2// This library implies ostrio:flow-router-title package, and both can be imported in single line: 3import { FlowRouterMeta, FlowRouterTitle } from 'meteor/ostrio:flow-router-meta';
Related Packages
flow-router-meta performs the best when used with the next packages:
- flow-router-title - Change document.title on the fly within FlowRouter-Extra
- flow-router-extra - Carefully extended FlowRouter
API
new FlowRouterMeta(FlowRouter)— The mainFlowRouterMetaconstructor that acceptsFlowRouteras the only argument
After new FlowRouterMeta(FlowRouter) instance is initiated it extends FlowRouter.router() and FlowRouter.group() methods and FlowRouter.globals with support of:
meta: Object— Object with meta-tagsmeta: function(params, qs, data) => object— Method returning object with meta-tagslink: Object— Object with link-tagslink: function(params, qs, data) => object— Method returning object with link-tagsscript: Object— Object with script-tagsscript: function(params, qs, data) => object— Method returning object with script-tags
Usage
You need to initialize FlowRouterMeta and FlowRouterTitle classes by passing FlowRouter object. Right after creating all your routes:
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2import { FlowRouterMeta, FlowRouterTitle } from 'meteor/ostrio:flow-router-meta'; 3 4FlowRouter.route('/', { 5 action() { /* ... */ }, 6 title: 'Title' 7 /* ... */ 8}); 9 10new FlowRouterMeta(FlowRouter); 11new FlowRouterTitle(FlowRouter);
Basic examples
Set only name and content attributes on meta tag:
1FlowRouter.route('/routePath', { 2 name: 'routeName', 3 meta: { 4 name: 'content' 5 } 6}); 7// Will generate 8// <meta name="name" content="content"> 9 10FlowRouter.route('/routePath', { 11 name: 'routeName', 12 meta: { 13 'og:title': 'Page title' 14 } 15}); 16// Will generate 17// <meta name="og:title" content="Page-title">
Set only rel and href attributes on link tag:
1FlowRouter.route('/routePath', { 2 name: 'routeName', 3 link: { 4 canonical: 'http://example.com' 5 } 6}); 7// Will generate 8// <link rel="canonical" href="http://example.com"> 9 10FlowRouter.route('/routePath', { 11 name: 'routeName', 12 link: { 13 rel: 'canonical', 14 href: 'http://example.com' 15 } 16}); 17// Will generate 18// <link rel="canonical" href="http://example.com">
Set multiple attributes on meta tag:
1FlowRouter.route('/routePath', { 2 name: 'routeName', 3 meta: { 4 uniqueName: { 5 name: 'name', 6 content: 'value', 7 property: 'og:name', 8 itemprop: 'name' 9 } 10 } 11}); 12// Will generate 13// <meta name="name" content="value" property="og:name" itemprop="name">
Set multiple attributes on link tag:
1FlowRouter.route('/routePath', { 2 name: 'routeName', 3 link: { 4 uniqueName: { 5 rel: 'name', 6 sizes: 'value', 7 href: 'http://value', 8 type: 'value-type' 9 } 10 } 11}); 12// Will generate 13// <link rel="name" sizes="value" href="http://value" type="value-type">
ldjson
This method uses special property named innerHTML which set script's content instead of attribute. This method and property can be used in the any other case when you need to set script's contents.
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2 3FlowRouter.route('/fourthPage', { 4 name: 'fourthPage', 5 title: 'Fourth Page title', 6 script: { 7 ldjson: { 8 type: 'application/ld+json', 9 innerHTML: JSON.stringify({ 10 '@context': 'http://schema.org/', 11 '@type': 'Recipe', 12 name: 'Grandma\'s Holiday Apple Pie', 13 author: 'Elaine Smith', 14 image: 'http://images.edge-generalmills.com/56459281-6fe6-4d9d-984f-385c9488d824.jpg', 15 description: 'A classic apple pie.', 16 aggregateRating: { 17 '@type': 'AggregateRating', 18 ratingValue: '4', 19 reviewCount: '276', 20 bestRating: '5', 21 worstRating: '1' 22 } 23 }) 24 } 25 }, 26 action() { /*...*/ } 27});
Use function as value
Properties of meta, link, and script tags can be a function that will execute upon navigation
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2 3FlowRouter.route('/routePath', { 4 name: 'routeName', 5 meta: { 6 url: { 7 property: 'og:url', 8 itemprop: 'url', 9 content() { 10 return document.location.href; 11 } 12 } 13 }, 14 link: { 15 canonical() { 16 return document.location.href; 17 } 18 } 19});
Use function context
data can get passed from data() hook. Read about data hook.
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2 3FlowRouter.route('/post/:_id', { 4 name: 'post', 5 waitOn(params) { 6 return [Meteor.subscribe('post', params._id)]; 7 }, 8 async data(params) { 9 return await Collection.Posts.findOneAsync(params._id); 10 }, 11 meta: { 12 keywords: { 13 name: 'keywords', 14 itemprop: 'keywords', 15 content(params, query, data) { 16 return data?.keywords || 'default, key, words'; 17 } 18 } 19 }, 20 title(params, query, data) { 21 if (data) { 22 return data.title; 23 } 24 return '404: Page not found'; 25 } 26});
Set CSS and JS per route
Load CSS and JS files on per route and per group basis.
[!IMPORTANT] Once CSS or JS is loaded there's no way to "unload" its code. This package will remove tags from head when navigated to other routes, but contents of loaded JS and CSS files will remain in browser's memory
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2 3// Set default JS and CSS for all routes 4FlowRouter.globals.push({ 5 link: { 6 twbs: { 7 rel: 'stylesheet', 8 href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' 9 } 10 }, 11 script: { 12 twbs: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js' 13 } 14}); 15 16// Rewrite default JS and CSS, for second route, via controller: 17FlowRouter.route('/secondPage', { 18 name: 'secondPage', 19 action() { 20 return this.render('layout', 'secondPage'); 21 }, 22 link: { 23 twbs: { 24 rel: 'stylesheet', 25 href: 'https://maxcdn.bootstrapcdn.com/bootstrap/2.2.0/css/bootstrap.min.css' 26 } 27 }, 28 script: { 29 twbs: 'https://maxcdn.bootstrapcdn.com/bootstrap/2.2.0/js/bootstrap.min.js' 30 } 31}); 32 33// Unset defaults, via controller: 34FlowRouter.route('/secondPage', { 35 name: 'secondPage', 36 action() { 37 return this.render('layout', 'secondPage'); 38 }, 39 link: { 40 twbs: null 41 }, 42 script: { 43 twbs: null 44 } 45}); 46 47// Rewrite default JS and CSS, for route group: 48const group = FlowRouter.group({ 49 link: { 50 twbs: { 51 rel: 'stylesheet', 52 href: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha/css/bootstrap.min.css' 53 } 54 }, 55 script: { 56 twbs: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha/js/bootstrap.min.js' 57 } 58}); 59 60group.route('/groupPage1', { 61 name: 'groupPage1', 62 action() { 63 return this.render('layout', 'groupPage1'); 64 } 65});
Bootstrap configuration
Push default meta, link, or script tags to FlowRouter.globals
1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; 2 3FlowRouter.globals.push({ 4 name: 'routeName', 5 meta: { 6 // <meta charset="UTF-8"> 7 charset: { 8 charset: 'UTF-8' 9 }, 10 11 // <meta name="keywords" content="Awes.."> 12 keywords: { 13 name: 'keywords', 14 itemprop: 'keywords', 15 content: 'Awesome, Meteor, based, app' 16 }, 17 18 // <meta name="description" itemprop="description" property="og:description" content="Default desc.."> 19 description: { 20 name: 'description', 21 itemprop: 'description', 22 property: 'og:description', 23 content: 'Default description' 24 }, 25 image: { 26 name: 'twitter:image', 27 itemprop: 'image', 28 property: 'og:image', 29 content: 'http://example.com' 30 }, 31 'og:type': 'website', 32 'og:title'() { 33 return document.title; 34 }, 35 'og:site_name': 'My Awesome Site', 36 url: { 37 property: 'og:url', 38 itemprop: 'url', 39 content() { 40 return window.location.href; 41 } 42 }, 43 'twitter:card': 'summary', 44 'twitter:title'() { 45 return document.title; 46 }, 47 'twitter:description': 'Default description', 48 'twitter:site': { 49 name: 'twitter:site', 50 value: '@twitterAccountName' 51 }, 52 'twitter:creator': { 53 name: 'twitter:creator', 54 value: '@twitterAccountName' 55 }, 56 'http-equiv': { 57 'http-equiv': 'X-UA-Compatible', 58 content: 'IE=edge,chrome=1' 59 }, 60 robots: 'index, follow', 61 google: 'notranslate' 62 }, 63 link: { 64 // <link href="https://maxcdn.bootstrapcdn.com/..." rel="stylesheet"> 65 stylesheet: 'https://maxcdn.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css', 66 67 // <link rel="canonical" href="http://example.com"> 68 canonical() { 69 return document.location.href; 70 }, 71 72 // <link rel="image" sizes="500x500" href="http://example.com"> 73 image: { 74 rel: 'image', 75 sizes: '500x500', 76 href: 'http://example.com' 77 }, 78 publisher: 'http://plus.google...', 79 'shortcut icon': { 80 rel: 'shortcut icon', 81 type: 'image/x-icon', 82 href: 'http://example.com' 83 }, 84 'icon': { 85 rel: 'icon', 86 type: 'image/png', 87 href: 'http://example.com' 88 }, 89 'apple-touch-icon-144': { 90 rel: 'apple-touch-icon', 91 sizes: '144x144', 92 href: 'http://example.com' 93 }, 94 'apple-touch-icon-114': { 95 rel: 'apple-touch-icon', 96 sizes: '114x114', 97 href: 'http://example.com' 98 }, 99 'apple-touch-icon-72': { 100 rel: 'apple-touch-icon', 101 sizes: '72x72', 102 href: 'http://example.com' 103 }, 104 'apple-touch-icon-57': { 105 rel: 'apple-touch-icon', 106 sizes: '57x57', 107 href: 'http://example.com' 108 } 109 }, 110 script: { 111 twbs: 'https://maxcdn.bootstrapcdn.com/bootstrap/2.3.2/js/bootstrap.min.js', 112 d3: { 113 src: 'https://d3js.org/d3.v3.min.js', 114 charset: 'utf-8' 115 } 116 } 117});
Running Tests
- Clone this package
- In Terminal (Console) go to directory where package is cloned
- Then run:
Meteor/Tinytest
# Default meteor test-packages ./ # With custom port meteor test-packages ./ --port 8888 # With local MongoDB and custom port MONGO_URL="mongodb://127.0.0.1:27017/flow-router-meta-tests" meteor test-packages ./ --port 8888
Support this project:
- Upload and share files using ☄️ meteor-files.com — Continue interrupted file uploads without losing any progress. There is nothing that will stop Meteor from delivering your file to the desired destination
- Use ▲ ostr.io for Server Monitoring, Web Analytics, WebSec, Web-CRON and SEO Pre-rendering of a website
- Star on GitHub
- Star on Atmosphere
- Sponsor via GitHub
- Support via PayPal