Simple SEO-Ready Meteor Boilerplate with Iron Router
August 3, 2019 6:50 pmSetting up a brand new Meteor app is as simple as running meteor create
but setting it up to have routing and indexable pages is more time-consuming. So I created a simple boilerplate I can use to get started quickly.
Assumptions & technology stack choices:
- You’re familiar with Meteor basics
- You’re ok running the production environment on Meteor’s Galaxy hosting (starting at ~$30/mo)
- Using Blaze for templating
- Using Iron Router for routing
If you want to clone the boilerplate repo, just run these 2 commands:
git clone https://github.com/meteorhubdotnet/meteor-iron-router-seo-boilerplate.git
meteor npm install --save @babel/runtime
If you want to manually code it, here are the steps. First, run:
meteor create --bare MY_PROJECT_NAME_GOES_HERE
cd MY_PROJECT_NAME_GOES_HERE
meteor
And let’s update the package list for our project – first let’s replace static HTML with Blaze templating:
meteor remove static-html
meteor add blaze-html-templates
Then let’s add a few packages:
meteor add iron:router mdg:seo meteorhubdotnet:seo gadicohen:sitemaps underscore
Then create the following folder structure:
My Project
|-- client
|-- head.html
|-- layout.html
|-- page-home.html
|-- page-home.js
|-- page-home.css
|-- page-2.html
|-- page-2.js
|-- page-2.css
|-- server
|-- sitemap.js
|-- public
|-- private
|-- routes
|-- route-for-page-home.js
|-- route-for-page-2.js
|-- router-options.js
|-- router-seo.js
Now let’s start working on our homepage. In the client
folder, add head.html
:
<!-- /client/head.html -->
<head>
<title>MY_PROJECT_NAME_GOES_HERE</title>
<meta name="viewport" content="initial-scale=1, width=device-width, height=device-height, viewport-fit=cover">
</head>
And in page-home.html
and page-2.html
let’s add some (very) basic code:
<!-- /client/page-home.html -->
<template name="pageHome">
<h1>Home</h1>
<a href="{{pathFor 'page2'}}">Go to page 2</a>
</template>
<!-- /client/page-2.html -->
<template name="page2">
<h1>Page 2</h1>
<a href="{{pathFor 'pageHome'}}">Go to home</a>
</template>
In layout.html
we must add the {{>yield}}
template helper for Iron Router:
<!-- /client/layout.html -->
<template name="layout">
{{>yield}}
</template>
Now that we have some HTML for our layout and our pages, let’s configure our routes:
// /routes/router-options.js
Router.configure({
layoutTemplate: 'layout',
});
// /routes/route-for-page-home.js
import { Router } from 'meteor/iron:router';
Router.route(
'/',
{
// Route name
name: 'pageHome',
// Include in sitemap?
sitemap: true,
// Crawl request frequency
changefreq: 'daily',
// Crawl priority
priority: '1.0',
// Activate pre-rendering of meta info
ironMeta: true,
// Meta info
meta() {
return {
title: 'META_TITLE_GOES_HERE',
description: 'META_DESCRIPTION_GOES_HERE',
keywords: 'META_KEYWORDS_GO_HERE',
canonical: 'CANONICAL_URL_GOES_HERE',
};
},
},
);
// /routes/route-for-page-2.js
import { Router } from 'meteor/iron:router';
Router.route(
'/page-2',
{
// Route name
name: 'page2',
// Include in sitemap?
sitemap: true,
// Crawl request frequency
changefreq: 'monthly',
// Crawl priority
priority: '0.1',
// Activate pre-rendering of meta info
ironMeta: true,
// Meta info
meta() {
return {
title: 'META_TITLE_FOR_PAGE_2_GOES_HERE',
description: 'META_DESCRIPTION_FOR_PAGE_2_GOES_HERE',
keywords: 'META_KEYWORDS_FOR_PAGE_2_GO_HERE',
canonical: 'CANONICAL_URL_FOR_PAGE_2_GOES_HERE',
};
},
},
);
At this point, the app works and renders the page metadata, but only when the page first loads. We want our metadata to get updated whenever the route changes, so let’s add some code to router-seo.js
in our routes
folder:
// /routes/router-seo.js
import { Router } from 'meteor/iron:router';
Router.onStop(function() {
if (Meteor.isClient) {
// Remove twitter stuff
// Remove Facebook stuff
$('[seo="meteorhubdotnet"]').remove();
}
});
Router.onAfterAction(function() {
if (Meteor.isClient) {
const meta = this.lookupOption('meta');
if (typeof meta === 'function') {
this.meta = _.bind(meta, this);
} else if (typeof meta !== 'undefined') {
this.meta = function() {
return meta;
};
}
if (meta) {
const metaData = this.meta();
if (metaData) {
$('[seo="meteorhubdotnet"]').remove();
_.each(metaData, function (val, key) {
if (key === 'title') {
document.title = val;
} else if (key === 'canonical') {
$('head').append(`<link rel="canonical" href="${val}" seo="meteorhubdotnet">`);
} else {
// Inject.meta(key, val, res);
let idType = 'name';
// Twitter like the name attribute whilst standard dictates property
if (key.slice(0, 2) === 'og' || key.slice(0, 2) === 'fb') {
idType = 'property';
}
$('head').append(`<meta ${idType}="${key}" content="${val}" seo="meteorhubdotnet"></meta>`);
}
});
}
}
}
});
Now let’s add a sitemap to our website. In the sitemap.js
files, let’s add:
// /routes/router-sitemap.js
Meteor.startup(function() {
sitemaps.add('/sitemap.xml', function () {
const out = [];
Router.routes.forEach(function (route) {
if (route.options && route.options.sitemap) {
if (route.path()) {
out.push({
page: route.path(),
lastmod: new Date(),
changefreq: route.options.changefreq ? route.options.changefreq : 'monthly',
priority: route.options.priority ? route.options.priority : '0.5',
});
}
}
});
return out;
});
});
Now if you go to http://localhost:3000/sitemap.xml you will see a sitemap with 1 entry for each route in your app.
And finally, we can deploy to Galaxy and our app will get picked up by search engines!
Categorised in: JavaScript, Meteor
This post was written by Yacine