Skip to main content
Community Plugin
View plugin on GitHub
See starters using this

gatsby-plugin-excerpts

This plugin provides a powerful way to extract excerpts from content. The excerpts might be useful for providing content previews on listing pages or for generating SEO descriptions.

Features

  • Works with any source/transformer (including gatsby-transformer-remark)
  • Locate excerpts using CSS selectors
  • Limit excerpts to a certain character/word count while preserving formatting (thanks to truncate-html)
  • Apply common transformations to your excerpts, such as removing images or stripping links
  • Retrieve multiple different excerpts for each node
  • Specify fallback behaviour if your preferred excerpt can’t be found
  • Fully-typed via TypeScript

Documentation

Configuration options are documented via TSDoc in the ConfigurationTypes file.

If you’re using TypeScript, you can import these types to get documentation within your IDE. (E.g. via IntelliSense)

// At the top of gatsby-config.ts
import { demand } from 'ts-demand'; // Optional, but makes it easier to apply typechecking to your config
import { ConfigurationTypes as ExcerptConfigurationTypes } from 'gatsby-plugin-excerpts';

// Then, in your plugin list
module.exports = {
    // ...
    plugins: [
        // ...
        {
            resolve: `gatsby-plugin-excerpts`,
            options: demand<ExcerptConfigurationTypes.IExcerptsPluginConfiguration>({
                // ...
            })
        }
    ]
};

Example

This example adds an excerpt called ‘snippet’ to pages generated by gatsby-transformer-remark and allows content authors to mark snippets using gatsby-remark-custom-blocks. If the author has not marked any snippets, the first 80 words will be used instead, excluding any images or gatsby-remark-prismjs code blocks.

The snippet will have all links stripped, and the headings will be adjusted down one level. (So h2 becomes h3, h3 becomes h4, etc…) This might be useful for a blog, as on a post page the post title will probably be an h1, while on a listing you might have the listing title as an h1 and each post title as an h2.

gatsby-remark-custom-blocks config (must be within the gatsby-transformer-remark plugins object)

{
    "resolve": "gatsby-remark-custom-blocks",
    "options": {
        "blocks": {
            "snippet": {
                "classes": "snippet"
            },
        },
    },
}

gatsby-plugin-excerpts config

{
    "resolve": "gatsby-plugin-excerpts",
    "options": {
        "sources": {
            "snippetBlocks": {
                "type": "htmlQuery",
                "sourceField": "html",
                "excerptSelector": ".custom-block.snippet .custom-block-body",
                "stripSelector": "a",
                "elementReplacements": [
                    {
                        "selector": "h6",
                        "replaceWith": "strong"
                    },
                    {
                        "selector": "h5",
                        "replaceWith": "h6"
                    },
                    {
                        "selector": "h4",
                        "replaceWith": "h5"
                    },
                    {
                        "selector": "h3",
                        "replaceWith": "h4"
                    },
                    {
                        "selector": "h2",
                        "replaceWith": "h3"
                    },
                ],
            },
            "default": {
                "type": "htmlQuery",
                "sourceField": "html",
                "excerptSelector": "html > *",
                "ignoreSelector": "img, .gatsby-highlight",
                "stripSelector": "a",
                "elementReplacements": [
                    {
                        "selector": "h6",
                        "replaceWith": "strong"
                    },
                    {
                        "selector": "h5",
                        "replaceWith": "h6"
                    },
                    {
                        "selector": "h4",
                        "replaceWith": "h5"
                    },
                    {
                        "selector": "h3",
                        "replaceWith": "h4"
                    },
                    {
                        "selector": "h2",
                        "replaceWith": "h3"
                    },
                ],
                "truncate": {
                    "length": 80,
                    "byWords": true,
                    "ellipsis": "…"
                },
            }
        },
        "sourceSets": {
            "markdownHtml": [
                "snippetBlocks",
                "default"
            ]
        },
        "excerpts": {
            "snippet": {
                "type": "html",
                "nodeTypeSourceSet": {
                    "MarkdownRemark": "markdownHtml"
                }
            }
        }
    },
}

Display in components

Then you can just add ‘snippet’ to your GraphQL queries to retrieve the excerpt called ‘snippet’.

query {
    site {
        siteMetadata {
            title
        }
    }
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
        edges {
            node {
                snippet
                html
                fields {
                    slug
                }
                frontmatter {
                    date(formatString: "YYYY-MM-DD")
                    title
                }
            }
        }
    }
}

If your excerpt’s output type is “html” (which it is in this example) then you will want to render it in your template/component using dangerouslySetInnerHTML.

<div className="snippet" dangerouslySetInnerHTML={{ __html: article.snippet }} />

Usage in content

Now when you write content, you can specify blocks to include within the snippet.

When I was a child, I used to spend long hours thinking about how every blog post needed a paragraph of irrelevant drivel at the start of it. Such content satisfies the content writer's desire to tell a story with their post, and they narcissistically convince themselves that others would want to read this story. But the problem is that when that blog post appears on a listing page or in search engine results, the excerpt often just displays the first few characters of the post. If the post starts with inanity the viewer may not click on the link.

[[snippet]]
| Wouldn't it be nice if you could retrieve excerpts from anywhere within the post? If the author could *choose* which paragraphs would be used as the snippet?
| 
| Well now you can.

Thanks to the useful Gatsby plugin gatsby-plugin-excerpts, you can create custom rules that blah blah blah blah...

The paragraphs within the snippet block (the 2nd and 3rd paragraphs) will be used as the snippet.

FAQ

Couldn’t I achieve the same thing within my template’s JavaScript?

You can achieve the same end result within your template/component, but with additional performance/bandwidth costs. When a user first visits your site, the initial content is delivered to them via a static HTML file. However as they navigate around your site, subsequent page loads are achieved by GraphQL query results being delivered via AJAX and the template being rendered clientside.

Because searching for excerpts within content requires the entire content to be loaded, this means that if a user visits a listing page that contains excerpts for 25 other pages, the user’s browser must download the entire contents of those 25 other pages just to display the listing. This is wasteful.

Additionally, there is an (admittedly small) amount of computation required to locate the excerpts. Do you really want to force your user’s device to do extra work every time they load a page? Gatsby is about generating static sites, and this plugin allows you to statically generate your excerpts.

Why isn’t this a gatsby-remark-* plugin?

It would’ve been much easier to write this plugin to be a plugin for gatsby-transformer-remark and it would also be easier to use. But I think too much of the Gatsby ecosystem is exclusive to Remark users, and I wanted to show alternative transformers some love. You can use this plugin with any source & transformer.

Can I use this plugin to retrieve excerpts from Front Matter or other metadata?

Sorry, not currently. This plugin only allows retrieving excerpts from page content. Front Matter can already be delivered efficiently via GraphQL, so I don’t see it as a priority. If you have a use-case for retrieving excerpts from metadata within this plugin, please let me know and I might reconsider.

How many times did you misspell ‘excerpt’ while writing this plugin?

Too many. Hopefully none of the mistakes made it into Git. Also I definitely have semantic satiation for that word now.

Future plans

  • Add functionality for searching for excerpts within plaintext content
  • Add functionality for converting content before it hits a searcher (e.g. convert Markdown to HTML, or HTML to plaintext)
  • Add system for using GraphQL arguments to modify excerpt behaviour
  • Add support for chaining together multiple sources/processors (maybe turn this into a general purpose content transmogrification engine?)
  • Find a better way to resolve values of other GraphQL fields (if possible)