How to implement SEO on Meteor and other Javascript based client side rendered webpages.

React (Facebook) Angular (Google) Blaze (Meteor) are three common Javascript Frameworks.

After three days of work, I finally got an effective SEO implementation on my self hosted Meteor website So I thought I would share some of my findings here for others.

Anyone with a Meteor page has probably gone View → Source and been met with just a bunch of Javascript files. This is because Meteor renders HTML dynamically which is terrible for Search Engine Optimisation (SEO) as Google (and others) will not be hanging around while your page is loaded in.

All is not lost however, and you can get all the SEO goodness of a static website with Meteor by following the steps below.

What you will need:

  1. manuelschoebel:ms-seo (
meteor add manuelschoebel:ms-seo

and follow the configuration steps. Note that SEO.config({}) sets some default values. SEO.set({}) is what you can use in the onBeforeAction() method on each page to set page specific SEO Data.

Note: you will also need to install phantom-js

Add “phantomjs”: “2.1.7” to your package.json file and run meteor npm install to install it.

To know if this is working set the title for a few pages and you should see the TITLE change in the TAB at the top of your browser when navigating to that page.

Router.route('/about', {onBeforeAction: function(){SEO.set({title: "About",meta: {'description': 'FastX is the fastest way to convert cryptocurrency into cash.',},og: {'title': "About",'description': `FastX is Australia's best value cryptocurrency marketplace. Buy, Sell & Trade - Bitcoin, Ethereum, Litecoin, Nano, Ripple. No fees, Instant payments. Live market rates.`,'image': ""}});;},action: function() {import "./imports/ui/pages/about.js";this.render('about');}});

Make sure you set the viewport in the SEO.config! Otherwise Google/spiders wont know that your site is optimised for mobile.

Meteor.startup(function() {if(Meteor.isClient){return SEO.config({title: 'FastX | Sell Bitcoin, Ethereum, Litecoin & Ripple',meta: {'description': `Buy, Sell & Trade - Bitcoin, Ethereum, Litecoin, Nano, Ripple. No fees, Instant payments. Live market rates. Australia's best value marketplace`,'viewport': "width=device-width, initial-scale=1"},og: {'description': 'No fees, Instant payments, Live market rates','image': ''}});}console.log("PhantomJs Working?:"+isSpiderable());});function isSpiderable(){return /PhantomJS/.test(window.navigator.userAgent);}

2. sitemap.xml: This file should contain all of the pages you want indexed. Once created you should upload this to your websites root directory, e.g

You can use this service to help generate your sitemap:

Once you have generated your sitemap.xml upload it to the Google Search Console → Sitemaps

3. This is a free service that will store cached copies of your sites fully rendered pages and serve them up to any search engine crawler that comes to your website. Go to and signup to get your <verification token>

meteor npm install prerender-node --save

On your server add the following code (this tells prerender-node package to redirect any traffic from these bots to to fetch the cached pages).

Meteor.startup(() => {let prerender = require(‘prerender-node’)prerender.set(‘prerenderToken’, '<VERIFICATION TOKEN>');prerender.set(‘protocol’, ‘https’);prerender.set(‘host’, ‘');prerender.crawlerUserAgents.push(‘googlebot’);prerender.crawlerUserAgents.push(‘bingbot’);prerender.crawlerUserAgents.push(‘yandex’);WebApp.rawConnectHandlers.use(prerender);}

Then in your main html file (where you have your <head></head> tags) insert

<script>window.prerenderReady = false;</script> <!-- Tells pre-render not to cache yet. -->

Then in each of your templates onRendered() events on each page, put the following code:

Template.your_template.onRendered(function() {
window.prerenderReady = true;//Tell pre-render we are now ready

Upload your new code to your server and go to pages and “Add URLs from sitemap” and import all of your pages.

Wait until the status changes from Caching.. to Cached to ensure that when you fetch as google it grabs the cached files.

4. Fetch As Google:

And ensure that your website property is added (the easiest way to verify is to add Google-Analytics code to your website and then upload).

Click on Fetch and Render

Wait for google to finish, then view the request. As you can see, Google sees the same mobile page that a human would see! Yay. This means that we are serving up the correct Viewport settings dynamically.

Go to to check that everything is working properly.

5. The Facebook Developer Tool provides a useful debugging interface you can use to check if all of your META data is being dynamically loaded properly:

Click show all raw tags to see a total list of META data tags that ms-seo has imported for you. If Google is giving you funky results (like not rendering for mobile properly, ensure your viewport tag is being inserted properly).

Once you are happy with how things appear in Fetch as Google. Click on the Request Indexing button. This will cause Google to re-index your website (with your nice new META data).

You can now also check your website in the Google Search Console → URL Inspection. You want to see all Green Ticks here. If it’s all green you can click on Request Indexing, which does the same thing as requesting an index in the Fetch as Google site.

Click on Live Test URL and ensure you have all green ticks here as well.

6. Setup a Google Business Listing: This will greatly increase the likelihood you will get a sidebar listing (that nice callout on the right hand side which shows your business and its details).

To setup a business listing go here:

7. Include Google Structured Data: Google Structured Data provides google with some additional information which it adds to your company profile which appears on the right hand side when someone searches for your site directly.

Google Structured Data Guide:

<!-- Google structured data --><script type="application/ld+json">//Organization{"@context": "","@type": "Organization","url": "","logo": ""},//Social Media{"@context" : "","@type" : "Organization","name" : "FastX","url" : "","sameAs" : ["","",""]},//Contact Point{"@context": "","@type": "Organization","url": "","contactPoint": [{ "@type": "ContactPoint","telephone": "+61422060805","contactType": "customer service"}]}</script><!-- End Google Structured Data -->

Are you a software developer using Meteor and have learned something not covered in this post? Let me know in the comments. I’d be interested to get your perspective!