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
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: {
always: true,
},
});
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>');
});
});