Skip to main content

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:

Meta priority

There is a three predefined priority levels, and you can always use custom:

  • 0 - used for default meta, available in constant META_PRIORITY_DEFAULT
  • 10 - used for meta from route config, available in constant META_PRIORITY_ROUTE
  • 20 - highest priority, intended for custom usage in application, usually for dynamic meta, available in constant META_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:

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;
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> tag
  • description - <meta name="description"> tag
  • keywords - <meta name="keywords"> tag
  • canonical - <link rel="canonical"> tag
  • viewport - <meta name="viewport"> tag
  • ogTitle - <meta property="og:title"> tag
  • ogDescription - <meta property="og:description"> tag
  • ogSiteName - <meta property="og:site_name"> tag
  • ogUrl - <meta property="og:url"> tag
  • ogType - <meta property="og:type"> tag
  • ogImage - <meta property="og:image"> tag
  • ogImageSecure - <meta property="og:image:secure_url"> tag
  • ogImageType - <meta property="og:image:type"> tag
  • ogImageAlt - <meta property="og:image:alt"> tag
  • ogImageWidth - <meta property="og:image:width"> tag
  • ogImageHeight - <meta property="og:image:height"> tag
  • ogLocale - <meta property="og:locale"> tag
  • twitterTitle - <meta name="twitter:title"> tag
  • twitterDescription - <meta name="twitter:description"> tag
  • twitterCard - <meta name="twitter:card"> tag
  • twitterSite - <meta name="twitter:site"> tag
  • twitterCreator - <meta name="twitter:creator"> tag
  • twitterImage - <meta name="twitter:image"> tag
  • twitterImageAlt - <meta name="twitter:image:alt"> tag
  • robots - 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>');
});
});