JSON-LD: Базовый блог

Опубликовано 0 комментариев 116 просмотров
JSON-LD: Базовый блог

Привет.

JSON-LD - один из методов передачи данных. Работает по тому же принципу, что и вариация schema.org, интегрируемая напрямую в HTML. В освновном полезна для поисковиков, которые видя структуру страницы отображают ее в более читабельном виде. Удобен он тем, что все данные передаются в JSON, что избавляет меня от необходимости изменять шаблон сайта.

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

Перед началом создадим класс, методы которого будут возвращать массивы с необходимой разметкой.

<?php

namespace App\Helpers;

class JsonLd
{

}

Person

Данный тип разметки подходит для главной страницы или "Обо мне".

 {
      "@context": "http://schema.org",
      "@type": "Person",
      "address": {
        "@type": "PostalAddress",
        "addressLocality": "Colorado Springs",
        "addressRegion": "CO",
        "postalCode": "80840",
        "streetAddress": "100 Main Street"
      },
      "colleague": [
        "http://www.example.com/JohnColleague.html",
        "http://www.example.com/JameColleague.html"
      ],
      "email": "mailto:[email protected]",
      "image": "janedoe.jpg",
      "jobTitle": "Research Assistant",
      "name": "Jane Doe",
      "alumniOf": "Dartmouth",
      "birthPlace": "Philadelphia, PA",
      "birthDate": "1979.10.12",
      "height": "72 inches",
      "gender": "female",
      "memberOf": "Republican Party",
      "nationality": "African American",
      "telephone": "(123) 456-6789",
      "url": "http://www.example.com",
      "sameAs" : [
        "https://www.facebook.com/",
        "https://www.linkedin.com/",
        "http://twitter.com/",
        "http://instagram.com/",
        "https://plus.google.com/"
      ]
}

Добавим его в наш класс.

public function about()
{
    return [
        '@context' => 'http://schema.org',
        '@type' => 'Person',
        'name' => config('app.name'),
        'url' => config('app.url'),
        'jobTitle' => 'PHP Developer',
        'gender' => 'male',
        'image' => asset('images/me.jpg'),
        'email' => 'mailto:[email protected]',
        'birthDate' => '1995-07-12T00:00:00+00:00',
        'sameAs' => array_values(config('gtxtymt.social', []))
    ];
}

Небольшое отступление - Яндекс утверждает, что все даты должны отображаться в ISO 8601. Тем не менее Гугл ничего против коротких YYYY-MM-DD не имеет.

Blog

Данный тип подходит для главной страницы блога. Содержит в себе информацию об организации (см. Organization) и список записей, выводимых на странице.

{
    "@context": "http://schema.org",
    "@type": "Blog",
    "name": "Blog name",
    "url": "https://example.com",
    "description": "Same as meta description",
    "publisher": {
        // Organization
    },
    "sameAs": [
        "https://facebook.com/BlogPage",
        "https://plus.google/BlogPage"
    ],
    "potentialAction": {
        "@type": "SearchAction",
        "target": "https://example.com/search.php?q={search_term}",
        "query-input": "required name=search_term"
    },
    "blogPosts": [
        {
            // BlogPosting
        }
    ]
}
public function blog(array $items)
{
    return [
        '@context' => 'http://schema.org',
        '@type' => 'Blog',
        'publisher' => $this->publisher(),
        'blogPost' =>
            array_map([$this, 'blogPosting'], $items)
    ];
}

Содержит вызов publisher(), указанный ниже. Принимает массив $items содержащий записи модели Post с записями текущей страницы. Проходит по каждому элементу массива и возвращает структурированный список записей методом, объявленным дальше.

BlogPosting

Предназначен для описания записи в блоге. Используется как непосредственно на странице записи, так и в предыдущем методе для вывода списка записей.

{
    "@context": "http://schema.org", 
     "@type": "BlogPosting",
     "headline": "14 Ways Json Can Improve Your SEO",
     "alternativeHeadline": "and the women who love them",
     "image": "http://example.com/image.jpg",
     "award": "Best article ever written",
     "editor": "John Doe", 
     "genre": "search engine optimization", 
     "keywords": "seo sales b2b", 
     "wordcount": "1120",
     "publisher": "Book Publisher Inc",
     "url": "http://www.example.com",
     "datePublished": "2015-09-20",
     "dateCreated": "2015-09-20",
     "dateModified": "2015-09-20",
     "description": "We love to do stuff to help people and stuff",
     "articleBody": "You can paste your entire post in here, and yes it can get really really long.",
     "author": {
        // Person
     }
 }
public function blogPosting(Post $post)
{
    return [
        '@context' => 'https://schema.org',
        '@type' => 'BlogPosting',
        'headline' => $post->name,
        'description' => $post->preview_text,
        'datePublished' => $post->created_at->toIso8601String(),
        'dateModified' => $post->updated_at->toIso8601String(),
        'mainEntityOfPage' => true,
        'url' => $post->link,
        'image' => [
            '@type' => 'ImageObject',
            'url' => url(\Storage::url($post->image)),
            'width' => 1200,
            'height' => 300
        ],
        'author' => $this->about(),
        'publisher' => $this->publisher()
    ];
}

В качестве параметра ожидается модель Post, данные из которой подставляются в поля. Содержит вызовы about() и publisher() (о нем ниже).

Хлебные крошки. Совершенно ничем не отличаются по предназначению и действию от аналогов с HTML-разметкой :) Могут использоваться везде.

{
    "@context": "http://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
        {
            // ListItem
        }
    ]
}
public function breadcrumb(array $items)
    {
        return [
            '@context' => 'http://schema.org',
            '@type' => 'BreadcrumbList',
            'itemListElement' =>
                array_merge(
                    [
                        [
                            '@type' => 'ListItem',
                            'position' => 1,
                            'item' => [
                                '@id' => route('index'),
                                'name' => config('app.name')
                            ]
                        ]
                    ],
                    array_map([$this, 'listItem'], $items)
                )
        ];
    }

Самый "кодообильный" метод. Для удобства я создал класс, с которым работаю как в контроллере, так и в шаблонах.

<?php

namespace App\Helpers;

use Illuminate\Contracts\Support\Arrayable;

class Breadcrumb implements Arrayable
{
    protected $items = [];

    public function add(string $name, string $url, bool $absolute = true)
    {
        $this->items[] = [
            'name' => $name,
            'url' => $absolute ? $url : url($url),
            'position' => count($this->items) + 1
        ];

        return $this;
    }

    public function toArray()
    {
        return (new JsonLd())->breadcrumb($this->items);
    }
}

Для удобного доступа из любого места в AppServiceProvider в методе boot привязал класс к контейнеру, а для удобной работы в контроллерах забиндил вызов класса.

$this->app->singleton('breadcrumb', function() {
    return new Breadcrumb();
});

$this->app->bind(Breadcrumb::class, function($app) {
    return $app['breadcrumb'];
});

Разберу на примере блога.

<?php

namespace App\Http\Controllers;

use App\Helpers\Breadcrumb;
use App\Helpers\JsonLd;
use App\Models\Post;
use App\Models\Tag;
use Illuminate\Http\Request;

class BlogController extends Controller
{
    public function __construct(Breadcrumb $breadcrumb)
    {
        $breadcrumb->add(__('title.blog'), route('blog.index'));
    }

    public function index(Request $request)
    {
        return view('blog.index');
    }

    public function single(Breadcrumb $breadcrumb, string $slug)
    {
        $item = Post::first();
        $breadcrumb->add($item->name, $item->link);

        return view('blog.single', compact('item'));
    }

    public function tag(Request $request, Breadcrumb $breadcrumb, string $slug)
    {
        $item = Tag::first();
        $breadcrumb->add($item->name, $item->link);

        return view('blog.index');
    }
}

Первая крошка добавления в классе JsonLd, название и адрес берутся из конфига. В __construct() определяем вторую хлебную крошку - страницу "Блог". В методах single() и tag() отвечающих за вывод подстраниц с одной записью и записями по тегу соответственно добавляем следующую хлебную крошку с информацией о страницах - названием и ссылкой. В index() это не требуется, так как мы уже объявили данную страницу в __construct(). Для вывода крошек используeтся app('breadcrumb')->toArray().

Дополнительные методы

Organization

Используется в blogPosting(). Обязателен для некоторых методов. Я не организация, что что поделать ¯_(ツ)_/¯

{
    "@context": "http://schema.org",
    "@type": "Organization",
    "@id": "https://example.com#organization",
    "url": "https://example.com",
    "name": "Awesome Organization",
    "description": "Add a description if you want.",
    "sameAs": [
        "facebook.com/Page",
        "plus.google.com/page",
        "twitter.com/page"
    ]
}
protected function publisher()
{
    return [
        '@type' => 'Organization',
        'name' => config('app.name'),
        'logo' => [
            '@type' => 'ImageObject',
            'url' => asset('images/logo.png')
        ]
    ];
}

ListItem

Используется в breadcrumbs(). Содержит информацию об элементе хлебных крошек - позиция, название, ссылка.

{
    "@type": "ListItem",
    "position": 1,
    "item": {
        "@id": "http://example.com",
        "name": "Website Name"
    }
}
protected function listItem(array $item)
{
    return [
        '@type' => 'ListItem',
        'position' => $item['position'] + 1,
        'item' => [
            '@id' => $item['url'],
            'name' => $item['name']
        ]
    ];
}

Выводим информацию

Полученные массивы необходимо преобразовать в JSON и вывести в head страницы. У меня для этого в родительском шаблоне определена область header, куда я их и вывожу.

<html lang="{{ config('app.locale') }}">
<head>
    @stack('header')
</head>
<body>
    Hello, world!
</body>
</html>
@push('header')
    <script type="application/ld+json">@json([$about, app('breadcrumb')->toArray()])</script>
@endpush

Да, данные должны находиться в script[type="application/ld+json"]. Возможно выводить как один из методов, так и несколько - завернув их в массив.

Заключение

JSON-LD - довольно легкий в освоении метод. Вместо темной тучи правок HTML он позволил мне обернуть все в красивый JSON. Здесь описаны лишь несколько вариантов разметки, которыми воспользовался я сам и которые пригодились на данном сайте. По факту любой из методов schema.org можно использовать с JSON-LD.

Во время тестирования можно воспользоваться инструментами для отладки - Гугл Яндекс. Надеюсь, данная статья вам помогла :)

А еще у меня есть канал в Telegram, где я публикую новости PHP, мемы и свежее из блога.
Открывай @gtxtymt_xyz и подписывайся! 👍

В ответ на сообщение

Доступна разметка Markdown. А еще вы можете использовать крутой пак эмоций.

Нажимая на кнопку «Отправить» вы даете свое согласие на обработку персональных данных в соответствии с законом №152-ФЗ «О персональных данных» от 27.07.2006 и принимаете условия Политики конфеденциальности.