Универсальний (изоморфный) «шлем» для React js или Как удобно работать с head на React js

  • Tutorial
image

Ребята из nfl вылечили одну из болей React js, работу с head. Речь пойдет о библиотеке react-helmet. Она работает как на клиенте, так и на сервере.

В предыдущей статье я писал о бойлер-плейте, в котором уже использутся react-helmet, поэтому возьму его:

       git clone https://github.com/BoryaMogila/koa_react_redux.git;
       npm install;
       npm run-script run-with-build;

Для тех, у кого своя сборка, ставим модуль:

      npm install --save react-helmet

Подключаем в своем в компоненте:

import { Component } from 'react'
import Helmet from "react-helmet"

class SomeComponent extends Component {
    render(){
        return (
            <div>
                <Helmet
                htmlAttributes={{"lang": "en", "amp": undefined}} // amp takes no value
                title="My Title"
                titleTemplate="MySite.com - %s"
                defaultTitle="My Default Title"
                base={{"target": "_blank", "href": "http://mysite.com/"}}
                meta={[
                    {"name": "description", "content": "Helmet application"},
                    {"property": "og:type", "content": "article"}
                ]}
                link={[
                    {"rel": "canonical", "href": "http://mysite.com/example"},
                    {"rel": "apple-touch-icon", "href": "http://mysite.com/img/apple-touch-icon-57x57.png"},
                    {"rel": "apple-touch-icon", "sizes": "72x72", "href": "http://mysite.com/img/apple-touch-icon-72x72.png"}
                ]}
                script={[
                  {"src": "http://include.com/pathtojs.js", "type": "text/javascript"},
                  {"type": "application/ld+json", innerHTML: `{ "@context": "http://schema.org" }`}
                ]}

               //Ваш код
            </div>
        );
    }

Helmet можно использовать в компонентах любой степени вложености, при этом свойства, заданные в компоненте ниже уровнем, будут перетирать свойства, заданные в компоненте уровнем выше.

class SomeComponent extends Component {
        render(){
               return (
                      <div>
                          <Helmet
                           title="My Title"
                           meta={[
                           {"name": "description", "content": "Helmet application"}
                           ]}
                           link={[
                               {"rel": "apple-touch-icon", "href": "http://mysite.com/img/apple-touch-icon-57x57.png"},
                               {"rel": "apple-touch-icon", "sizes": "72x72", "href": "http://mysite.com/img/apple-touch-icon-72x72.png"}
                           ]}
                           base={{"href": "http://mysite.com/"}}
                           />
                          <AnotherComponent />
                      </div>
               )
        }
}

class AnotherComponent extends Component {
        render(){
               return (
                      <div>
                          <Helmet
                          title="Nested Title"
                          meta={[
                          {"name": "description", "content": "Nested component"}
                          ]}
                          link={[
                               {"rel": "apple-touch-icon", "href": "http://mysite.com/img/apple-touch-icon-180x180.png"}
                          ]}
                          base={{"href": "http://mysite.com/blog"}}
                          />
                      </div>
               )
        }
}

В итоге получим:

<head>
    <title>Nested Title</title>
    <meta name="description" content="Nested component">
    <link rel="apple-touch-icon" href="http://mysite.com/img/apple-touch-icon-180x180.png">
    <base href="http://mysite.com/blog">
</head>

Для тайтла есть возможность задать шаблон:

<Helmet
    defaultTitle="My Site"
    titleTemplate="My Site - %s"
/>

<Helmet
    title="Nested Title"
/>

Результат:

<head>
    <title>My Site - Nested Title</title>
</head>

Создание тега script:

<Helmet
    script={[{
        "type": "application/ld+json",
        "innerHTML": `{
            "@context": "http://schema.org",
            "@type": "NewsArticle"
        }`
    }]}
/>

Результат:

<head>
    <script type="application/ld+json">
      {
          "@context": "http://schema.org",
          "@type": "NewsArticle"
      }
    </script>
</head>

Создание тега style:

<Helmet
    style={[{
        "cssText": `
            body {
                background-color: green;
            }
        `
    }]}
/>

Результат:

<head>
    <style>
        body {
            background-color: green;
        }
    </style>
</head>

Для получения данных для head на сервере нужно вызвать метод rewind() после ReactDOM.renderToString или ReactDOM.renderToStaticMarkup.

Возвращенный объект head имеет семь возможных параметров:

  • htmlAttributes
  • title
  • base
  • meta
  • link
  • script
  • style

Они имеют два метода toComponent() и toString().

Преобразование данных в строку:

let markup = ReactDOM.renderToString(<Handler />);
let head = Helmet.rewind();

const html = `
    <!doctype html>
    <html ${head.htmlAttributes.toString()}>
        <head>
            ${head.title.toString()}
            ${head.meta.toString()}
            ${head.link.toString()}
        </head>
        <body>
            <div id="content">
                 ${markup}
            </div>
        </body>
    </html>`
//ответ сервера
ctx.body = html;

Решение в стиле React:

let markup = ReactDOM.renderToString(<Handler />);
let head = Helmet.rewind();
function HTML () {
    const attrs = head.htmlAttributes.toComponent();

    return (
        <html {...attrs}>
            <head>
                {head.title.toComponent()}
                {head.meta.toComponent()}
                {head.link.toComponent()}
            </head>
            <body>
                <div id="content">
                    // React stuff here
                </div>
            </body>
        </html>
    );
}
//ответ сервера
ctx.body = ReactDOM.renderToString(<HTML />);

Готовые рабочие примеры для использования:


P.S. Как всегда рад услышать ваши замечания и дополнения.
  • +12
  • 9,5k
  • 6
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 6
  • 0

    Меня смущает, то вложенный компонент затирает title родителя вместо того, чтобы добавлять значение вначало через разделитель. Костыль с titleTemplate действует лишь на один уровень.

    • 0
      С вами согласен, как мы знаем идеальных решений нет. Но думаю не составит труда написать на пару символов больше.
    • 0

      А можете так же пояснить для чего может понадобиться обновлять base, link и meta теги уже после загрузки страницы? Ведь для того чтобы обновить заголовок document.title = 'Новый заголовок' не обязательно тянуть отдельную библиотеку.

      • +1
        Первый момент изоморфность, document.title на сервере не работает. Второй, представим что вы хотите нормальний шаринг, притом разный для разных соц сетей. У нас это выглядит премерно так
        <title>Новостройки Украины</title>
            <meta name="title" content="Новостройки Украины" />
            <meta name="description" content="Каталог квартир в новостройках. Купить недорогую квартиру в новостройке от застройщика можно на сайте DOM.RIA." />
        
            <meta property="og:title" content="Новостройки Украины" />
            <meta property="og:description" content="Каталог квартир в новостройках. Купить недорогую квартиру в новостройке от застройщика можно на сайте DOM.RIA." />
            <meta property="og:type" content="website" />
            <meta property="og:url" content="https://dom.ria.com/ru/%D0%9D%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8/" />
            <meta property="og:locale" content="ru_UA"/>
            <meta property="og:locale:alternate" content="uk_UA"/>
            <meta property="og:site_name" content="DOM.RIA.com" />
            
            
            
            <meta property="og:image:width" content="620" />
            <meta property="og:image:height" content="460" />
            
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/0/92/9249/9249fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/148/14876/14876fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/0/92/9286/9286fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/0/95/9572/9572fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/107/10732/10732fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/115/11563/11563fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/129/12993/12993fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/130/13082/13082fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/0/75/7562/7562fl.jpg" />
            
                <meta property="og:image" content="https://cdn.riastatic.com/photos/dom/newbuild_photo/1/131/13176/13176fl.jpg" />
            
        
            <meta property="fb:app_id" content="*******" />
            <!-- for yandex webmaster -->
            <meta name='yandex-verification' content='********' />
        
        
            
        
            
            <link rel="alternate" hreflang="uk-UA" href="https://dom.ria.com/uk/%D0%9D%D0%BE%D0%B2%D0%BE%D0%B1%D1%83%D0%B4%D0%BE%D0%B2%D0%B8/"/>
            
        
            
            <link rel="alternate" hreflang="ru-UA" href="https://dom.ria.com/ru/%D0%9D%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8/"/>
        
      • 0
        Создаем себе проблему, а потом героически ее решаем :)
        • 0
          задачи создают менеджера, а мы их решаем.)))

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.