hexo-covers

Microbrowsers cover generator for Hexo static site generator

Usage no npm install needed!

<script type="module">
  import hexoCovers from 'https://cdn.skypack.dev/hexo-covers';
</script>

README

hexo-covers Publish on NPM

hexo-covers is a plugin for Hexo static site generator that generates microbrowser page cover so you'll have compelling webpage preview while sharing the link via iMessage, WhatsApp, Telegram, Facebook and others.

As more of our conversations happen in group chats and slack channels, link previews are an important way for you to engage users before they start the journey on your site. To help users take the leap and visit your site, we need to make sure that all our pages are annotated with microdata. Better yet, we can use these previews to create compelling visual summaries.

Colin BendellMicrobrowsers are Everywhere

  • Generates preview covers for your website.
  • Produces web-optimized, compressed images.
  • Allows to easily customize template.
  • Supports generating covers for posts, pages, tags, and categories.

An example

How it works

  1. The plugin scans all pages, posts, categories, and tags.
  2. For every item hexo-covers runs Chromium via puppeteer and takes the screenshot.
  3. Every screenshot is compressed and put into the cache folder.
  4. During the website build all covers are included in the output build.
  5. You add page metadata using resolve_cover, resolve_tag_cover, resolve_category_cover helpers. For more information see below.

Requirements

  • Hexo: 4.x
  • Node 12+

Usage

  1. Once the plugin is installed and enabled, it will scan the website content during the build.
  2. Covers will be generated for all posts, pages, tags, and categories using default templates. To do that hexo-covers runs Chromium via puppeteer for every cover and takes the screenshot.
  3. Once the processing of the cover is done, the result files will be stored in a special folder (/.covers/) and included in the cache manifest (/.covers/covers.json). You should not care about the folder structure in this folder. Ensure that .covers folder added to your repo. If it's ignored, cover processing will start each time, which is time-consuming.
  4. If the post title doesn't fit the image, an error message will be generated in the console during the build. It guarantees that you won't have "broken" previews for any page.
  5. Once hexo-covers generated the covers, you'll need to specify special meta tags so microbrowsers could discover it (for more information see below).
  6. You post your link via messengers or social networks and see a nice preview 🎉

Defining page metadata

The crucial part of the adding previews is page metadata. You can find more information about it here and here. In general, you'll need to add few tags into the <head> section:

<html>
    <head>
        ...
        <meta property="og:title" content="Website title" />
        <meta name="twitter:title" content="Website title />
        <link rel="image_src" href="< link to your image preview >" />
        <meta name="twitter:image:src" content="< link to your image preview >" />
        <meta property="og:image" content="< link to your image preview >" />
    </head>
    <body>
        ...
    </body>
</html>

In addition, you can add a few more tags like og:type, og:description, og:image:type, og:image:width, og:image:height, article:author, twitter:description, twitter:site, twitter:card and others, but it's completely up to you.

To add this, you'll need to modify layout.ejs template in your theme folder. I personally prefer to determine layout type and render some partial there. This is how <head> section of the layout.ejs template could look:

<head>
    ...
    <%_  let layout = page.layout;
    if (!layout) {
        if (page.tag) {
            layout = 'tag';
        } else if (page.category) {
            layout = 'category';
        }
    } _%>
    <%_ if (layout) { _%>
    <%- partial(`_partial/microbrowsers/${layout}`) %>
    <%_ } _%>
</head>

Adding this snippet triggers partial render for every type of layout, e.g. _partial/microbrowsers/page.ejs will be generated for page layout, _partial/microbrowsers/post.ejs will be generated for post layout, etc.

Here are a few examples of how to define these partials:

post.ejs and page.ejs: template for post/page

resolve_cover tag helper is used to retrieve information about the cover image.

<%_ const cover = resolve_cover() _%>
<meta property="og:type" content="article" />
<meta property="og:url" content="<%= page.permalink %>" />
<meta property="og:title" content="<%= page.title %>" />
<meta property="og:description" content="<%= page.description %>" />
<%_ if (cover) { _%>
    <link rel="image_src" href="<%= full_url_for(cover.file) %>" />
    <meta property="og:image" content="<%= full_url_for(cover.file) %>" />
    <meta property="og:image:type" content="image/<%= cover.type %>" />
    <meta property="og:image:width" content="<%= cover.dimensions.w %>" />
    <meta property="og:image:height" content="<%= cover.dimensions.h %>" />
<%_ } _%>
<meta name="twitter:title" content="<%= page.title %>" />
<meta name="twitter:description" content="<%= page.description %>" />
<meta property="twitter:url" content="<%= page.permalink %>" />
<meta name="twitter:card" content="summary_large_image" />
<%_ if (cover) { _%>
    <meta name="twitter:image:src" content="<%= full_url_for(cover.file) %>" />
<%_ } _%>

tag.ejs: template for tag

resolve_tag_cover tag helper should be used instead of ``resolve_cover`.

<%_ const cover = resolve_tag_cover(page.tag) _%>
<meta property="og:type" content="article" />
<meta property="og:title" content="<%= page.tag %>" />
<%_ if (cover) { _%>
    <link rel="image_src" href="<%= full_url_for(cover.file) %>" />
    <meta property="og:image" content="<%= full_url_for(cover.file) %>" />
    <meta property="og:image:type" content="image/<%= cover.type %>" />
    <meta property="og:image:width" content="<%= cover.dimensions.w %>" />
    <meta property="og:image:height" content="<%= cover.dimensions.h %>" />
<%_ } _%>
<meta name="twitter:title" content="<%= page.tag %>" />
<meta name="twitter:card" content="summary_large_image" />
<%_ if (cover) { _%>
    <meta name="twitter:image:src" content="<%= full_url_for(cover.file) %>" />
<%_ } _%>

category.ejs: template for category

resolve_category_cover tag helper should be used instead of ``resolve_cover`.

<%_ const cover = resolve_category_cover(page.category) _%>
<meta property="og:type" content="article" />
<meta property="og:title" content="<%= page.category %>" />
<%_ if (cover) { _%>
    <link rel="image_src" href="<%= full_url_for(cover.file) %>" />
    <meta property="og:image" content="<%= full_url_for(cover.file) %>" />
    <meta property="og:image:type" content="image/<%= cover.type %>" />
    <meta property="og:image:width" content="<%= cover.dimensions.w %>" />
    <meta property="og:image:height" content="<%= cover.dimensions.h %>" />
<%_ } _%>
<meta name="twitter:title" content="<%= page.category %>" />
<meta name="twitter:card" content="summary_large_image" />
<%_ if (cover) { _%>
    <meta name="twitter:image:src" content="<%= full_url_for(cover.file) %>" />
<%_ } _%>

Additional data for pages and posts

You can specify custom data for every page that will be passed in the template during the build. To do that add the cover key in page/post frontmatter:

---
title: Test post
cover:
    title: My test post
    image: wp3060116.jpg
    hide_logo: false
    hide_title: false
    hide_subtitle: false
    disable_fade: false
    raw: false
---

Post text

There are few predefined key that you can pass in frontmatter:

  • title — overrides title of the post in the cover
  • image — custom background image for the cover
  • hide_logo — hides blog logo
  • hide_title — hides post title
  • hide_subtitle — hides post sub-title
  • disable_fade — disables background fading
  • raw — do not process the cover with standard workflow, just copy image instead

You can use these params if you use the default template. You can also specify your own keys - all of them will be passed into the template as a query string params. It's useful when you build a custom template and want to show more data.

Additional data for tags and categories

By default covers for tags and categories generated with name that passed from the URL (e.g. for /tag/aspnet the name will be aspnet). If you want to display a friendly name for tags and categories, you can specify it in an additional data file.

To do that add special configuration key source.categories.data and source.tags.data and specify a relative path to the data files (find more information in Configuration section below).

Here is an example of how you can define the data file:

aspnet:
    title: ASP.NET
    background: aspnet.webp

frontend:
    title: Frontend Development
    background: frontend.webp

In addition, you can specify background images and other custom keys that will be passed in the template file during the build via query string parameters. Also, you'll need to specify source.categories.images and source.tags.images parameters in configuration to be able to override cover background via data file.

Templates

How the default template works

The template is a regular HTML file. During the cover build hexo-covers starts web-server that serves the template. After that puppeteer is used to take a screenshot of the page.

There is a default template that looks like this way:

Default look of the template

If you are OK with the style of the default template, you do not need to do anything about it. If you want to customize the look of the cover, read the next section.

Customizing the template

If you want to customize the look of the cover, feel free to create an HTML file somewhere in the project folder. To let hexo-covers know where is your template located, specify the relative path in the templates section of configuration (see below). Optionally, you can define additional images that will be passed into the template. All paths are relative, so you can use any images from your project you want.

Use the default template as a reference on how to create your own template. Pay attention to JavaScript code in the default template. There is a code to extract params from the query string (which passed by hexo-covers) as well as the code that throws an error when title text is too large to render.

Any JavaScript error that throws on the template page will generate error in Hexo console during the build. It introduced intentionally to avoid generating "broken" covers.

Configuration

To configure the plugin add covers key to the Hexo config file. For example:

covers:
    enable: true
    title: 'Your blog title'
    base_dir: '.covers'
    manifestFileName: 'covers.json'
    include:
        - keywords
    tagsUrl: tag
    categoriesUrl: category
    compress: true
    source:
        categories:
            data: _data/categories.yml
            images: _covers/categories
        tags:
            data: _data/tags.yml
            images: _covers/tags
    templates:
        page:
            path: themes/theme1/layout/microbrowser-template/page.html
            images:
                logo: themes/theme1/source/assets/favicon/favicon-194x194.png
                background: themes/theme1/layout/microbrowser-template/bg.svg
            dimensions:
                width: 964
                height: 504
        post:
            path: themes/theme1/layout/microbrowser-template/post.html
            images:
                logo: themes/theme1/source/assets/favicon/favicon-194x194.png
                background: themes/theme1/layout/microbrowser-template/bg.svg
            dimensions:
                width: 964
                height: 504
        category:
            path: themes/theme1/layout/microbrowser-template/category.html
            images:
                logo: themes/theme1/source/assets/favicon/favicon-194x194.png
                background: themes/theme1/layout/microbrowser-template/bg.svg
            dimensions:
                width: 964
                height: 504
        tag:
            path: themes/theme1/layout/microbrowser-template/tag.html
            images:
                logo: themes/theme1/source/assets/favicon/favicon-194x194.png
                background: themes/theme1/layout/microbrowser-template/bg.svg
            dimensions:
                width: 964
                height: 504
Key Required Default value Description
enable false true Flag to disable plugin execution.
title false Your website title in Hexo configuration The website title value that will be passed into the template.
base_dir false .images Directory name to store cover cache.
manifestFileName false images.json File name to store cover cache manifest (for more info see below).
include false [ keywords ] Frontmatter keys that will be available at the template during preview generating.
tagsUrl false tag The URL where tag covers will be produced.
categoriesUrl false tag The URL where category covers will be produced.
compress false true Determines if output cover images will be compressed.
source.categories.data false _data/categories.yml Path to yaml file that provides additional data for categories (for more information see above).
source.categories.images false _covers/categories Path to images folder for categories (for more information see above).
source.tags.data false _data/tags.yml Path to yaml file that provides additional data for tags (for more information see above).
source.tags.images false _covers/tags Path to images folder for tags (for more information see above).
templates false Definition of templates for pages, posts, tags, and categories.
templates.page.path
templates.post.path
templates.category.path
templates.tag.path
true Path to default template Relative path to template file.
templates.page.images
templates.post.images
templates.category.images
templates.tag.images
false { background: "lib/templates/bg.svg" } Images that will be passed into template during generating.
templates.page.dimensions
templates.post.dimensions
templates.category.dimensions
templates.tag.dimensions
true { width: 964, height: 504 } Size of generated cover image for the template.

Manifest

Normally, you shouldn't care about the manifest structure. But if you're curious, the manifest is a JSON file that contains key-value collection of processed files. The key is a relative path to the image. The value is information about the processed cover. All items are grouped into sections — tags, categories, pages, posts.

Here is an example:

{
    "tags": {
        "dotnet": {
            "file": "tags/8ec70aeb/dotnet@cover.jpg",
            "size": 17476,
            "hash": "8ec70aebbc51e28b158a13af745f1bb3",
            "type": "jpg",
            "dimensions": {
                "w": 964,
                "h": 504
            }
        }
    },
    "categories": {
        "mobile": {
            "file": "categories/95306cf2/mobile@cover.jpg",
            "size": 28816,
            "hash": "95306cf2703054c1fe91c432f82a8721",
            "type": "jpg",
            "dimensions": {
                "w": 964,
                "h": 504
            }
        }
    },
    "pages": {
        "terms/index.md": {
            "file": "pages/83c8a774/index@cover.jpg",
            "size": 34955,
            "hash": "83c8a77498a457a187a007c7f4a408c7",
            "type": "jpg",
            "dimensions": {
                "w": 964,
                "h": 504
            }
        }
    },
    "posts": {
        "_posts/2014/fronttalks-2014.md": {
            "file": "posts/2c7f8a02/fronttalks_2014@cover.jpg",
            "size": 29735,
            "hash": "2c7f8a02c9c676e12487c0fc64e65c4f",
            "type": "jpg",
            "dimensions": {
                "w": 964,
                "h": 504
            }
        }
    }
}

In page template you can also access cover property that will contain the part of the manifest that related to the current page.