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:
Packages I use: spiderable, gadicohen:sitemaps, manuelschoebel:ms-seo
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>
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();
}
})
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;
});
Using meteor-up to deploy the app, it installs phantomJS. It combined with spiderable package enable google to crawl your app.
Problems I encounter:
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.
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.
What I'm doing good or wrong?
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.
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.
Related
Is it possible to define static meta data for each route in nuxt.config.js?
Suppose there is the following folder structure:
- pages
- examplepage.vue
- loremipsumpage.vue
- index.vue
the following is configured in nuxt.config.js:
head: {
title: 'Hi, I should only be displayed if nothing else is defined!',
meta: [
{ hid: 'description', name: 'description', content: 'Hi, I should only be displayed if nothing else is defined!' },
]
},
the following is configured in examplepage.vue:
head() {
return {
title: "Examplepage",
meta: [
{ hid: 'description', name: 'description', content: 'I wanna be placed in the generated html' },
]
}
},
And yes I know, that works in principle. When calling the page, the title and the meta tags are adjusted by the javascript. But not when generating my static examplepage/index.html file.
The following head is still generated there (dist/examplepage/index.html):
<head>
<title>Hi, I should only be displayed if nothing else is defined!</title>
<meta data-n-head="1" data-hid="description" name="description" content="Hi, I should only be displayed if nothing else is defined!">
.....
Is there a possibility to define fixed meta tags for certain routes which will be considered when generating the static html files? The data is not even dynamic. I only want to define static meta values for static routes.
Important notice:
I know that SSR would solve my problem.
But i would like to continue to run the site as SPA.
I have already tried various configurations in nuxt.config.js. However, all without success. In the Nuxt documentation I have also not found.
I have spent the better part of three days trying to get a Open Graph image generator working for my Next.js blog. After getting frustrated with hitting the 50mb function size limit I changed away from an API to a function call in the getStaticProps method of my pages/blog/[slug].tsx. This is working but now the issue is with the meta tags. I am dynamically setting them using the image path from the image generation function as well as information from the respective post. When I view the page source, I see all the appropriate tags and the open graph image has been generated and the path works but none of these tags are seen by crawlers. Upon checking the source file I realized that none of the head tags are pre-rendered. I am not sure if I am not understanding exactly what SSG does because I thought it would pre-render my blog pages (including the head). This seems like a common use case, and although I found some relevant questions on SO, I haven't found anyone really answering it. Is this an SSG limitation? I have seen tutorials for dynamic meta tags and they use SSR but that doesn't seem like it should be necessary.
// pages/blog/[slug].tsx
...
import Layout from '#/components/Layout';
import getOgImage from '#/utilities/ogImage';
...
export default function Post({ slug, locale, image, post, code }: Props): JSX.Element {
const metadata = {
...
};
...
return (
<Layout {...metadata}>
...
</Layout>
);
}
export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
...
const ogImage = await getOgImage(slug, locale, post.data.title);
return {
props: { slug, locale, image: ogImage, post: post.data, code },
};
};
export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
...
return {
paths,
fallback: false,
};
};
// Layout.tsx
...
import Meta from '#/components/Meta';
export default function Layout({ children, ...rest }: Props): JSX.Element {
return (
<>
<Meta {...rest} />
<Wrapper>
<Header />
<Content>{children}</Content>
</Wrapper>
</>
);
}
// Meta.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
...
const Meta: React.FC<Props> = ({
locale,
title,
image,
description,
author,
published,
modified,
section,
tags,
}: Props) => {
const router = useRouter();
const url = SITE_URL + router.asPath;
const ogType = router.pathname === '/' ? 'website' : 'article';
return (
<Head>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content={TWITTER_USERNAME} />
<meta property="og:url" content={url} />
<meta property="og:type" content={ogType} />
<meta property="og:site_name" content={SITE_NAME} />
<meta property="og:locale" content={locale} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} key="ogDescription" />
<meta property="og:image" content={image} key="ogImage" />
</Head>
);
};
Thanks for anyone who looked at my issue. I figured it out! The way I implemented my dark mode used conditional rendering on the whole app to prevent any initial flash. I have changed the way I do dark mode and everything is working now!
I have a docusaurus as front for my company website.
And I'm wondering, how to track clicks on the link to the login form in the administrator's page.
This link is in the siteconfig.js:
headerLinks: [
{href: 'https://demo.multifactor.ru', label: 'Demo'},
{doc: 'intro', label: 'Documentation'},
{doc: 'api', label: 'API'},
{href: '/login', label: 'Login'}
],
I've tried to add custom tag, like in the example below, but docusaurus ignore this construction
headerLinks: [
{href: 'https://demo.multifactor.ru', label: 'Demo'},
{doc: 'intro', label: 'Documentation'},
{doc: 'api', label: 'API'},
{href: '/login', label: 'Login', onClick: 'ga (‘send’, ‘event’, ‘submit’, ‘login_link’);'}
],
does anybody has ideas about how to do this?
which version of Docusaurus you are using? I only know how to config the google-analytics on Docusaurus.v2.
If you are using Docusaurus.v2, you can easily config the google-analytics by:
// docusaurus.config.js
module.exports = {
plugins: ['#docusaurus/plugin-google-analytics'],
themeConfig: {
googleAnalytics: {
trackingID: 'UA-141789564-1',
// Optional fields.
anonymizeIP: true, // Should IPs be anonymized?
},
},
};
or by:
// docusaurus.config.js
module.exports = {
presets: [
[
'#docusaurus/preset-classic',
{
// Will be passed to #docusaurus/plugin-google-analytics.
googleAnalytics: {
trackingID: 'UA-141789564-1',
// Optional fields.
anonymizeIP: true, // Should IPs be anonymized?
},
},
],
],
};
If you are using Docusarurus.v1, highly recommand you to migrate your website from v1 to v2 following instructions.
Also, how to migrate the google-analytics plugin is also mentioned in the doc.
No way at the moment. You can swizzle the components or just look at the number of page views for that page within Google Analytics.
There are two solutions from official page
plugin-google-analytics
plugin-google-gtag
That working great but i faced an issue when i cannot install these packages properly because one of my projects behind corporate firewall, so i decided to place my analytics snippet into Head component (docs)
<Head>
<script
src={ANALYTICS.SCRIPT_URL}
async={true}
></script>
<script>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', ${ANALYTICS.G_TAG});
ga('send', 'pageview');
`}
</script>
</Head>
this is my code in gatsby-config.js
module.exports = {
siteMetadata: {
title: `title`,
description: ``,
author: `#Wavii`,
},
plugins: [
{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: "UA-XXXXXX-XX",
// Defines where to place the tracking script - `true` in the head and `false` in the body
head: true,
},
},
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
don't know why it is not working, it is not even showing any google analytics on my source code.
Thanks in advance.
I can't get it working in production either. I've also tried https://www.gatsbyjs.org/packages/gatsby-plugin-gtag/ - which I hear is the "newer" way, and I can get some results using Helmet like so:
<Helmet>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXX-X"></script>
<script type="application/ld+json">{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('config', 'UA-XXX-X');
`}</script>
</Helmet>
... but I don't have it successfully testing in production yet. I've installed all the google analytics plugins and though debug works in the case of Helmet, I'm not really getting good results otherwise.
use gatsby-plugin-google-gtag with the following configuration for the google analytics 4 support.
Put it at the top of the plugins list, and I was not able to get it to work without "anonymize_ip: true".
plugins: [
{
resolve: `gatsby-plugin-google-gtag`,
options: {
trackingIds: [
"GA-TRACKING_ID", // Google Analytics / GA
],
pluginConfig: {
head: true,
anonymize_ip: true,
},
},
},
// other plugins
],
For me it did not work because I was using ad blocker browser plugin, which was blocking all analytics data. After disabling the plugin everything worked like a charm.
"gatsby-plugin-google-analytics" generate obsolete "ga"
switch to "gatsby-plugin-google-gtag" is generate "gtag"
reference
https://developers.google.com/analytics/devguides/collection/gtagjs/migration
I am trying to follow the tutorial for Google Sign-in . I have copied Google's example exactly, but when I press the sign-in button, an "Invalid Request" error is reported.
The Url for my page is ...
https://s3-ap-southeast-2.amazonaws.com/sbd-aws-sdk-delphi-22/index.html
The content of this page is ...
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title of the document</title>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<meta name="google-signin-scope" content="profile email">
<meta name="google-signin-client_id" content="230599269648-86peetl434op89ug41lg1vv8sjspfupp.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>
<script>
function onSignIn(googleUser) {
// Useful data for your client-side scripts:
var profile = googleUser.getBasicProfile();
console.log("ID: " + profile.getId()); // Don't send this directly to your server!
console.log('Full Name: ' + profile.getName());
console.log('Given Name: ' + profile.getGivenName());
console.log('Family Name: ' + profile.getFamilyName());
console.log("Image URL: " + profile.getImageUrl());
console.log("Email: " + profile.getEmail());
// The ID token you need to pass to your backend:
var id_token = googleUser.getAuthResponse().id_token;
console.log("ID Token: " + id_token);
};
</script>
</head>
<body>
Content of the document......
<div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
Sign out
<script>
function signOut() {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
});
}
</script>
</body>
</html>
The web application is properly registered with the Google Developer's Console. Here is the registration downloaded in a json format ...
{
"web": {
"client_id": "230599269648-86peetl434op89ug41lg1vv8sjspfupp.apps.googleusercontent.com",
"project_id": "test-federated-login-196400",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": (redacted),
"javascript_origins": [
"https://s3-ap-southeast-2.amazonaws.com"
]
}
}
When a user presses the sign-in button, the pop-up dialog reports a 400 error with text ...
400. That’s an error.
Error: invalid_request
Permission denied to generate login hint for target domain.
... with request details ...
redirect_uri=storagerelay://https/s3-ap-southeast-2.amazonaws.com?id=auth370793
response_type=permission id_token
scope=email profile openid
openid.realm=
client_id=230599269648-86peetl434op89ug41lg1vv8sjspfupp.apps.googleusercontent.com
ss_domain=https://s3-ap-southeast-2.amazonaws.com
fetch_basic_profile=true
gsiwebsdk=2
How do I get this basic example of Google Sign-in on a web page to work?
I have a work-around. I cannot explain why it works and the OP solution does not.
AWS bucket objects have two different URL formats:
https://s3-ap-southeast-2.amazonaws.com/sbd-aws-sdk-delphi-22
http://sbd-aws-sdk-delphi-22.s3-website-ap-southeast-2.amazonaws.com
I've used Google's webmaster Central to claim ownership and to verify ownership of two domains:
https://s3-ap-southeast-2.amazonaws.com/sbd-aws-sdk-delphi-22/
http://sbd-aws-sdk-delphi-22.s3-website-ap-southeast-2.amazonaws.com/
Although of course, I cannot claim https://s3-ap-southeast-2.amazonaws.com
The first form of URL is generally intended for use in API's. The second is intended for use in the browser (although in the second form, only http is allowed, not https).
I've then modified the javascript origins to add the second form ...
{
"web": {
"client_id": "230599269648-86peetl434op89ug41lg1vv8sjspfupp.apps.googleusercontent.com",
"project_id": "test-federated-login-196400",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": (redacted),
"javascript_origins": [
"https://s3-ap-southeast-2.amazonaws.com",
"http://sbd-aws-sdk-delphi-22.s3-website-ap-southeast-2.amazonaws.com"
]
}
}
... and then addressing the page in the browser by the second form ...
http://sbd-aws-sdk-delphi-22.s3-website-ap-southeast-2.amazonaws.com
... I can then sign-in.
I can't sign-out. The sign0out button raises javascript errors, but that is another question for another day.
Morale of the story
When implementing Google Sign-In on an Amazon AWS hosted bucket ...
Use the website hosting form of the url (http://bucket-name.s3-website-region-amazonaws.com)
Claim and verify ownership of the domain in Webmaster central (in the website hosting form)
Use the same url in javascript origins
Don't use the API form of the bucket url.