mhlavacka
mhlavacka

Reputation: 701

Implement SEO to Meteor app the right way

I was trying to find the right way to implement SEO in your Meteor app, but can't find any good examples. I feel like what I'm doing is working, but to some extent and could be way better. This is what I'm doing for SEO in my Meteor app:

  1. Packages I use: spiderable, gadicohen:sitemaps, manuelschoebel:ms-seo
  2. Head Tag :

<head>
	<meta charset="UTF-8" />
	<meta http-equiv="Content-Language" content="en-us" />
	<meta name="google" value="notranslate" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
	<meta name="mobile-web-app-capable" content="yes" />
	<meta name="google-site-verification" content="google-verification-id" />
	<meta name="msvalidate.01" content="bing-verification-id" />
</head>

  1. This is what I'm doing using ms-seo package: In seo.js file:

SeoCollection = new Mongo.Collection('SeoCollection');

Meteor.startup(function() {
    if (Meteor.isClient) {
        return SEO.config({
            title: ’title',
            meta: {
                    'description': ’siteDescription',
                    'keywords': ‘keyword, keyword, keyword',
                    'charset': 'utf-8',
                    'site_name': ’siteName',
                    'url':'http://siteName.com',
                    'viewport': 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
                    'X-UA-Compatible': 'IE=edge,chrome=1',
                    'HandheldFriendly': 'true',
                    'apple-mobile-web-app-capable' : 'yes',
                    'apple-mobile-web-app-status-bar-style': 'black',
                    'referrer': 'never',
              },
              og: {
                    'title': ’siteTitle',
                    'description': ’siteDescription',
                    'image': 'image.png',
                    'type': 'website',
                    'locale': 'en_us',
                    'site_name': 'siteName',
                    'url': 'http://sitename.com'
              },
            rel_author: 'https://plus.google.com/+appPage/'
        });
    }


    SeoCollection.update(
        {
            route_name: 'homepage'
        },
        {
            $set: {
                route_name: 'homepage',
                title: ’title',
                meta: {
                    'description': ’siteDescription',
                    'keywords': ‘keyword, keyword, keyword',
                    'charset': 'utf-8',
                    'site_name': ’siteName',
                    'url':'http://siteName.com',
                    'viewport': 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
                    'X-UA-Compatible': 'IE=edge,chrome=1',
                    'HandheldFriendly': 'true',
                    'apple-mobile-web-app-capable' : 'yes',
                    'apple-mobile-web-app-status-bar-style': 'black',
                    'referrer': 'never',
                },
                og: {
                    'title': ’siteTitle',
                    'description': ’siteDescription',
                    'image': 'image.png',
                    'type': 'website',
                    'locale': 'en_us',
                    'site_name': 'siteName',
                    'url': 'http://sitename.com'
                },
                rel_author: 'https://plus.google.com/+appPage/'
            }
        },
        {
            upsert: true
        }
    );

});

and dynamically using Iron:router -

this.route('page:data', {
    path: '/page',
    onBeforeAction: function() {

      SEO.set({
        title: data.title,
        meta: {
          'description': 'description',
        },
        og: {
          'title': data.title,
          'description': 'description',
          'image': data.image
        }
      })
      
      this.next();
    }
  })

  1. Submitting sitemaps using gadicohen:sitemaps:

sitemaps.add('/items.xml', function() {
  var out = [], pages = Collection.find().fetch();
  _.each(pages, function(page) {
    out.push({
      page: 'page/' + page.encodedUrl,
      lastmod: page.date,
    });
  });
  return out;
});

  1. Using meteor-up to deploy the app, it installs phantomJS. It combined with spiderable package enable google to crawl your app.

Problems I encounter:

  1. Spiderable package works only in Google. It's true that it has the largest market share, but this way for 30% of SEO traffic from other search engines it works really bad.

  2. I'm not sure if I should have all that stuff in seo.js file also in head tag. I know seo.js overwrites it, but when I request title from url on Reddit, it says no title tag find. It might be similar with other search engines from my perspective. But then there'd be multiple same tags, that is not good either.

  3. What I'm doing good or wrong?

Upvotes: 1

Views: 1352

Answers (2)

aedm
aedm

Reputation: 6584

You might want to consider FlowRouter SSR, which uses server-side rendering for HTTP requests. It generates the entire DOM on the server, and sends it as an initial static HTML <body>, thereby enabling all web spiders to crawl your site, not just Google. After that, your app will just continue to function as a real-time webapp, overriding the initial DOM.

It also supports subscriptions, thus you can use Mongo collections to render crawable content, too. But unfortunately it only works with React for now.

Upvotes: 1

Ethaan
Ethaan

Reputation: 11376

The best way to handle this (atleast on my case), was using prerender.io + manuelschoebel:ms-seo in this way.

How?

Installing prerender, here you can use.

meteor add dfischer:prerenderio

NOTE If you get

res.send(status, body): Use res.status(status).send(body) instead

You will have to use the npm package itself.

Now Meta Tags.

for this you can create a function like this.

setSEO = function(seoData){
  SEO.set({
          title: seoData.title,
          meta: {
            'description': seoData.description
          },
          og: {
            'title': seoData.title,
            'description': seoData.description,
            'image':seoData.image,
            'fb:app_id':seoData.appId
          }
        });
};

And then just call it on the onAfterAction hook

 Router.map(function() {
  self.route('route.name', {
    path: '/some',
    template: 'test',
    name: 'route.name',
    onAfterAction: function() {
      setSEO(pageData.seoData);
    }
  });
});

And thats it, on my side its working on twitter, g+, facebook, linkedin, pinterest.

Upvotes: 3

Related Questions