SEO and Meta
Explanation
A separate module responsible for meta tags generation - SeoModule
from @tramvai/module-seo
package.
Data for meta tags can be defined globally, for the all application pages, or only for a specific routes.
All meta have a priority and the data with highest priority will be used.
Meta tags
Meta tags in tramvai
application represented as a key: value
object, where key is a name of the meta tag from predefined list
Data sources
Meta tags will be generated per every page request, and based on different data sources:
- Default meta pack, static and will be used for all pages
- Meta from route configuration, static and will be used for specific route
- Custom data source, when you need to get data from different places (services, stores, etc.)
- Dynamic meta, can be changed in Actions or CommandLineRunner steps
- JSON-LD, sets up only for server requests
Meta priority
There is a three predefined priority levels, and you can always use custom:
0
- used for default meta, available in constantMETA_PRIORITY_DEFAULT
10
- used for meta from route config, available in constantMETA_PRIORITY_ROUTE
20
- highest priority, intended for custom usage in application, usually for dynamic meta, available in constantMETA_PRIORITY_APP
Usage
Installation
Be sure that you have installed and connected SeoModule
(already included in new projects):
npx tramvai add @tramvai/module-seo
And connect in the project
import { createApp } from '@tramvai/core';
import { SeoModule } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [SeoModule],
});
Default meta
You can set default meta pack by using the method SeoModule.forRoot
with metaDefault
option:
import { createApp } from '@tramvai/core';
import { SeoModule } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [
SeoModule.forRoot({
metaDefault: {
title: 'Tramvai application',
},
}),
],
});
Another way is to provide META_DEFAULT_TOKEN
token directly:
import { createApp, provide } from '@tramvai/core';
import { SeoModule, META_DEFAULT_TOKEN } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [SeoModule],
providers: [
provide({
provide: META_DEFAULT_TOKEN,
useValue: {
title: 'Tramvai application',
},
}),
],
});
Route meta
Simplest way to set meta for specific route is to use static seo
property of the page component:
import type { PageComponent } from '@tramvai/react';
const CommentsPage: PageComponent = () => <h1>Comments Page</h1>;
CommentsPage.seo = {
metaTags: {
title: 'Comments Page Title',
},
};
export default CommentsPage;
Another way, for manually created routes is to update route config.seo
property:
const route = {
name: 'comments',
path: '/comments/',
config: {
pageComponent: '@/pages/comments',
seo: {
metaTags: {
title: 'Comments Page Title',
},
},
},
};
Custom data source
You can set additional data source by using the method SeoModule.forRoot
with metaUpdaters
option.
Each source is a function that takes a meta and allows you to extend the meta through a updateMeta
call. The priority is a positive number, for each specific meta key the value with the highest priority will be used, the value with priority 0
denotes the default value.
This meta update will be executed for every request for application pages. Possible use-case - read information for meta from some services or stores (this information need to be already fetched, meta updaters are syncronyous).
import { createApp } from '@tramvai/core';
import { SeoModule, META_PRIORITY_ROUTE } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [
SeoModule.forRoot({
metaUpdaters: [
(meta) => {
meta.updateMeta(META_PRIORITY_ROUTE, {
ogTitle: 'Tramvai application',
});
},
],
}),
],
});
Another way is to provide META_UPDATER_TOKEN
token directly:
import { createApp, provide } from '@tramvai/core';
import { SeoModule, META_UPDATER_TOKEN, META_PRIORITY_ROUTE } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [SeoModule],
providers: [
provide({
provide: META_UPDATER_TOKEN,
useValue: (meta) => {
meta.updateMeta(META_PRIORITY_ROUTE, {
ogTitle: 'Tramvai application',
});
},
}),
],
});
Dynamic meta
For example, your meta depends on API response. Actions is a good place to both save response to store and update meta tags by using META_WALK_TOKEN
токен:
import { declareAction } from '@tramvai/core';
import { META_WALK_TOKEN, META_PRIORITY_APP } from '@tramvai/module-seo';
declareAction({
name: 'action',
fn() {
this.deps.meta.updateMeta(META_PRIORITY_APP, {
title: 'WoW, such dynamic!',
});
},
deps: {
meta: META_WALK_TOKEN,
},
conditions: {
dynamic: true,
},
});
JSON-LD
It's possible to set up JSON-LD
for your routes. You can learn more about this technology here.
There are two ways to set it up:
- use static
seo
property of the page component
import type { PageComponent } from '@tramvai/react';
const CommentsPage: PageComponent = () => <h1>Comments Page</h1>;
CommentsPage.seo = {
meta: {
structuredData: {
jsonLd: {
'@context': 'http://schema.org/',
'@type': 'Comments',
},
},
},
};
export default CommentsPage;
- use route configuration with manually created routes
const route = {
name: 'comments',
path: '/comments/',
config: {
pageComponent: '@/pages/comments',
seo: {
meta: {
structuredData: {
jsonLd: {
'@context': 'http://schema.org/',
'@type': 'Comments',
},
},
},
},
},
};
Remember, that JSON-LD
only generates for server requests, so it won't change on SPA transitions.
How-to
How to set a custom meta tag?
Custom meta tag description, usage example:
import { createApp, provide } from '@tramvai/core';
import { SeoModule, META_UPDATER_TOKEN, META_PRIORITY_ROUTE } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [SeoModule],
providers: [
provide({
provide: META_UPDATER_TOKEN,
useValue: (meta) => {
meta.updateMeta(META_PRIORITY_ROUTE, {
metaCustom: {
tag: 'meta',
attributes: {
name: 'metaCustomNameAttribute',
content: 'metaCustomContent',
},
},
});
},
}),
],
});
And result will be - <meta name="metaCustomNameAttribute" content="metaCustomContent" data-meta-dynamic="true">
How-to remove meta tag?
Just return null
as value for highest priority:
import { createApp, provide } from '@tramvai/core';
import { SeoModule, META_UPDATER_TOKEN, META_PRIORITY_ROUTE } from '@tramvai/module-seo';
createApp({
name: 'tincoin',
modules: [SeoModule],
providers: [
provide({
provide: META_UPDATER_TOKEN,
useValue: (meta) => {
meta.updateMeta(30, {
keywords: null,
});
},
}),
],
});
References
Meta tags list
Predefined list for easy adding meta tags:
title
-<title>
tagdescription
-<meta name="description">
tagkeywords
-<meta name="keywords">
tagcanonical
-<link rel="canonical">
tagviewport
-<meta name="viewport">
tagogTitle
-<meta property="og:title">
tagogDescription
-<meta property="og:description">
tagogSiteName
-<meta property="og:site_name">
tagogUrl
-<meta property="og:url">
tagogType
-<meta property="og:type">
tagogImage
-<meta property="og:image">
tagogImageSecure
-<meta property="og:image:secure_url">
tagogImageType
-<meta property="og:image:type">
tagogImageAlt
-<meta property="og:image:alt">
tagogImageWidth
-<meta property="og:image:width">
tagogImageHeight
-<meta property="og:image:height">
tagogLocale
-<meta property="og:locale">
tagtwitterTitle
-<meta name="twitter:title">
tagtwitterDescription
-<meta name="twitter:description">
tagtwitterCard
-<meta name="twitter:card">
tagtwitterSite
-<meta name="twitter:site">
tagtwitterCreator
-<meta name="twitter:creator">
tagtwitterImage
-<meta name="twitter:image">
tagtwitterImageAlt
-<meta name="twitter:image:alt">
tagrobots
- function with parameters(type: 'all' | 'noindex' | 'nofollow' | 'none')
which returns<meta name="robots" content="none">
,<meta name="robots" content="noindex, nofollow">
or<meta name="robots" content="noarchive">
tag
Custom meta tags
If you need to add meta tag which is not in the predefined list, you need to provide object with specific description:
type CustomTag = {
tag: string;
attributes?: {
[key: string]: any;
};
innerHtml?: string;
};
Testing
Testing work with META_UPDATER_TOKEN and META_DEFAULT_TOKEN
If you have a module or providers that define META_UPDATER_TOKEN
or META_DEFAULT_TOKEN
then it is convenient to use special utilities to test them separately:
import { Module, provide } from '@tramvai/core';
import { testMetaUpdater } from '@tramvai/module-seo/tests';
import { META_PRIORITY_APP, META_DEFAULT_TOKEN, META_UPDATER_TOKEN } from '@tramvai/module-seo';
describe('testMetaUpdater', () => {
it('modules', async () => {
const metaUpdater = jest.fn<
ReturnType<typeof META_UPDATER_TOKEN>,
Parameters<typeof META_UPDATER_TOKEN>
>((walker) => {
walker.updateMeta(META_PRIORITY_APP, {
title: 'test title',
});
});
@Module({
providers: [
provide({
provide: META_UPDATER_TOKEN,
multi: true,
useValue: metaUpdater,
}),
],
})
class CustomModule {}
const { renderMeta } = testMetaUpdater({
modules: [CustomModule],
});
const { render, metaWalk } = renderMeta();
expect(metaWalk.get('title').value).toBe('test title');
expect(render).toMatch('<title data-meta-dynamic="true">test title</title>');
});
it('providers', async () => {
const { renderMeta } = testMetaUpdater({
providers: [
provide({
provide: META_DEFAULT_TOKEN,
useValue: {
title: 'default title',
},
}),
],
});
const { render } = renderMeta();
expect(render).toMatch('<title data-meta-dynamic="true">default title</title>');
});
});
Known issues
Googlebot crawls and tries to index all links within your HTML
Googlebot will parse everything inside your HTML document, including inline scripts, and even JSON located within those scripts. If Googlebot encounters a link, it might extract and attempt to access these URLs to index them.
When you are developing a Tramvai application, the most common case where this could happen is with the initial state. This initial state is passed from your server to the client inside an inline script with the __TRAMVAI_STATE__
id. Such initial states can also contain links. More information about state, you can read in State Management.
Unfortunately, there is no straightforward solution at this moment.
Links:
- https://stackoverflow.com/q/47210596 - stackoverflow discussion
- https://support.google.com/webmasters/thread/217029647/json-data-in-next-js-app?hl=en - question in google support