I would recommend skipping entirely over any of the SEO packages, onRendered/onCreated, spiderable and all of that.
Spiderable is pretty hard to get right and if you do it wrong it can crash your servers for some really hard to debug reasons.
IMO the easiest and best solution is to do server side rendering.
With meteorhacks:ssr
and meteorhacks:picker
you can do all of that stuff incredibly easily.
Here is the approach that I use in production:
Make sure in your main.html
(where ever you keep your head
tags) you add:
<meta name="fragment" content="!">
otherwise the crawlers wont know it’s an ajax type site.
Then you can add the following.
<!-- private/layout.html -->
{{{getDocType}}}
<html lang="en">
<head>
<meta name="description" content="description"/>
{{> Template.dynamic template=template data=data}}
</head>
</html>
Then create the file to help on the server
// server/seo/layout.js
SSR.compileTemplate('seoLayout', Assets.getText('layout.html'));
// Blaze does not allow to render templates with DOCTYPE in it.
// This is a trick to made it possible
Template.seoLayout.helpers({
getDocType: function() {
return "<!DOCTYPE html>";
}
});
Once you have that setup you can then add individually the pages you want SEO on and you can specify exactly how you want the meta
tags set.
Example:
<!-- private/users.html -->
<title>{{user.profile.firstname}} {{user.profile.lastname}}</title>
<!-- Open Graph Meta Tags -->
<meta property="og:type" content="website"/>
<meta property="og:title" content="{{user.profile.firstname}} {{user.profile.lastname}}"/>
<meta property="og:description" content="{{userDescription user.profile.briefInfo}}"/>
<meta property="og:site_name" content="Your Website Here"/>
<meta property="og:url" content="https://yoururlhere.com/{{user.username}}"/>
<meta property="og:image" content="{{user.profile.profilePictureURL}}"/>
<!-- just testing this getting in here -->
<!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="{{user.profile.firstname}} {{user.profile.lastname}}"/>
<meta name="twitter:description" content="{{userDescription user.profile.briefInfo}}"/>
<meta name="twitter:image" content="{{user.profile.profilePictureURL}}"/>
<meta name="twitter:site" content="@yourhandle"/>
<meta name="twitter:creator" content="@{{userTwitter user.profile.twitterHandle}}"/>
Then you add the server side javascript to give you helpers.
// server/seo/users.js
SSR.compileTemplate('seoUser', Assets.getText('users.html'));
Template.seoUser.helpers({
userDescription:function(description){
if(description) {
return description;
} else {
return "default description";
}
},
userTwitter:function(handle){
if(handle) {
return handle;
} else {
return "default handle"
}
}
})
Then after that then you can set up your server side routes.
//server/serverRoutes.js
// ----------------------------------------------------------------------------
// picker definition. Use this to figure out if a crawler is hitting you
// ----------------------------------------------------------------------------
var seoPicker = Picker.filter(function(req, res) {
var isCrawler = [];
var string = req.headers['user-agent'];
isCrawler.push(/_escaped_fragment_/.test(req.url));
if(string){
isCrawler.push(string.indexOf('facebookexternalhit') >= 0);
isCrawler.push(string.indexOf('Slack') >= 0);
isCrawler.push(string.indexOf('Twitterbot') >= 0);
}
return isCrawler.indexOf(true) >= 0;
});
// Indexing user pages
seoPicker.route('/:username', function(params, req, res){
var user = Meteor.users.findOne({username:params.username});
var html = SSR.render('seoLayout',{
template:'seoUser',
data: {user:user}
});
res.end(html);
});
Then any time your route /:username
gets hit by a crawler it will automatically send the correct parsable html.