Hugo SEO / Social Partials

Here’s my first attempt at an SEO “plugin” for Hugo. I’m also hoping that this can be the start of standardizing some important variables, especially authors, images and videos.

Please let me know if there is anything I need to fix or can make better. Contributions welcome. :slight_smile:


To be more clear about the contents, this package implements:

  1. Open Graph
  2. Facebook Insights
  3. Twitter Cards

This is good, and similar to the approach I’m taking in cabaret (look specifically at layouts/partials/header/).

I think that, as a hugo community, we need to come up with common language (I’m okay switching to head if that is what everyone is going to use), but we also need to figure out how to make these sorts of plugins useful for submodule inclusion. Ideally, rather than having layouts/partials, you’ll have all of this so that I can submodule this as layouts/partials/head/seo).

I agree completely. I think it should come from the community… and should be guidelines, not rules. Perhaps providing a proposal for some guidelines would be a good start.

100% agreed, that’s my goal. What’s the best place / way to present a proposal? In my readme for this package, I outline my proposal for authors, images and video in .Site.Params and .Params.

@halostatue I saw your repo before doing this and it was my inspiration. :slight_smile: I just pushed new changes. Everything is better optimized and tested now, plus I added Google News keywords. I also adjusted the file structure as you suggested.

I’m very interested to see how you integrate this with submodules. I’ve avoided them to this point in favor of package management tools.

I like this.

I also like the idea of pulling this in as a git submodule, but as a submodule is just a special clone, you will inherit the folder structure. You may rename the root, but in this case it would be better if all files started at the top level.

This is how the path structure looks like on my site:


@DerekPerkins: if you do this as head/… (skip the layouts/partials), then I can do:

git submodule add layouts/partials

That means that I can say {{ partial 'layouts/partials/hugo-snippets/head/seo' }} and it will just work the way that we expect it to.

@bjornerik Thanks for the pull requests, I think we should be able to make this the standard for most themes, which will really help interoperability.

I could remove the layouts folder, but I anticipate adding shortcodes along the way as well. How would that work with the submodule strategy?

I think we will need to have those as separate repos, because of git limitations.

There’s no way to do something like a “union” between two directories named the same thing in git.

What we may need to do is figure out how to do this sort of layering for both themes (because I’d like to be able to sew in your SEO stuff into cabaret, etc.) and for individuals who want to do this. This probably requires something be added to hugo itself, maybe along the same lines as go get (e.g., hugo get)?

@spf13 your input here may be useful, because I think that for this sort of plugin, we may need to consider some sort of support in Hugo itself. I’m busy trying to get the automated header IDs work that I said that I’d be doing done, so I can’t look at it this week.

My two cents is that I think we should include these with Hugo itself and built in partials.

They seem general enough to have broad benefits. Shipping with Hugo still enables users to either 1. Not use them, 2. Use their own, 3. Override them.

Building them in also provides a bit of structure around partials that the themes can leverage.

Remember that the way the logic is for partials. Local partials will be used if they exist, then theme partials if they exist and lastly internal ones.

We may want to have them stabilize a bit before including them, but I think that’s the direction that it should go in.


@spf13 I’d love to contribute them to Hugo core. To be clear, this also includes standardizing on all of the metadata locations, correct?

  • .Site.Params.authors
  • .Site.Taxonomies.series
  • //It makes coding a little more complex, but if we made this an array from the beginning, Hugo could support multiple authors out of the box.
  • .Params.images
  • .Params.videos
  • .Params.news_keywords

With that in mind, I’d also like to promote .Site.Params.Authors to be handled the same as .Site.Taxonomies, and be accessible at .Site.Authors.

so I want a discussion around this. My ideas are.

Yes on Authors. Should be an array and I like the idea of promoting it.
Site.Author can be a method that simply returns the first one making it easy to work with single author sites without much of an issue.

We should really define a set of base fields for the Author entity… and permit the user to provide their own fields as well.

I’ve been working on prototypes of automatically adding media to a page. It’s nowhere near done and probably won’t make it into v0.13 but it’s something to keep in mind.

Here’s my recommendation for the Author entity.

  given_name          = "John"
  family_name         = "Connor"
  display_name        = "John Connor" // Not auto generated, just an option
  thumbnail     = ""
  images        = [
  short_bio     = "John Connor is the son of Sarah Connor and Kyle Reese..."
  long_bio      = "John Connor is the son of Sarah Connor and Kyle Reese, and the leader of the worldwide human resistance..."
  email         = ""

    website         = "killskynet"
    github          = "killskynet"
    facebook        = "killskynet"
    twitter         = "killskynet"
    googleplus      = "killskynet"
    pinterest       = "killskynet"
    instagram       = "killskynet"
    youtube         = "killskynet"
    linkedin        = "killskynet"
    skype           = "killskynet"

This is my proposal for .Site.Social. It kind of seems like it should be promoted the same as Authors.

github          = "nozzle"
facebook        = "nozzleio"
facebook_admin  = "17807199"  # This needs to be a page admin to get domain insights
twitter         = "nozzleio"
twitter_domain  = "" # This domain shows in twitter cards as "View on `twitter_domain`"
googleplus      = "NozzleIo"
pinterest       = "nozzleio"
instagram       = "nozzleio"
youtube         = "nozzleio"
linkedin        = "nozzleio"

Here’s my proposal for .Params. @spf13 - I really like the idea of having methods that can be easily used to grab the first entry as a variable that can be used to access data. In fact, since we’re defining .Site.Authors, we could make a function called .AuthorDetails or something that would perform the map index to .Site.Authors and return a single author object with all the fields defined in .Site.

A similar idea would be great for images and videos, where you could immediately get access to the first one, rather than having to use the more cumbersome {{ index ... }} syntax.

I’m interested to see what your thoughts are in this regards. I was thinking somewhat the reverse, that Hugo could take embedded images and auto-add them to the end of the images array. My thought was that once we set up the standard metadata location, each individual theme could auto embed where it makes sense. The use cases that come to mind are:

  1. Featured Images - used as a thumbnail in summary views, etc. They would use the first image in the array as the featured image.
  2. Galleries - A theme could easily embed a slideshow, gallery or other grouping of media.
  3. Image preprocessing - Having arrays of data makes some other things almost trivial, like image preprocessing. The user / theme could define some standard sizes that would be autogenerated on a Hugo build. The theme could then refer to those specific sizes with some standard methods like .Params.Images[1].Large.
    // This needs to map to one of the authors setup in the .Site.Params
    authors      = [ "jconnor", "sconnor" ]

    // An array of urls to any images referenced in the post. The first image in the array will be the default shareable image.
    images       = [

    // An array of urls to any videos referenced in the post. The first video in the array will be the default shareable video.
    videos       = [

    // The first 10 will be inserted into the Google "news_keyword" meta tag
    news_keywords = [ "skynet", "terminator", "john connor" ]

I have some suggestions, too, but am without desktop internet for the evening. It may be a couple of days before I can put this to virtual paper (I have some standard social icons and would use them handily).

You can see some ideas on how this works from cabaret.

What would be even better than just having image links is to also store some metadata in the front matter. If it were an array of image objects that also included things like title, caption, alt, etc, that would ease gallery creation, plus we could add those images to the sitemap like this:

		<image:title><![CDATA[secret agent1]]></image:title>
		<image:caption><![CDATA[secret agent1]]></image:caption>
		<image:title><![CDATA[google adwords tool]]></image:title>
		<image:caption><![CDATA[google adwords tool]]></image:caption>
		<image:title><![CDATA[seo link analysis]]></image:title>
		<image:caption><![CDATA[seo link analysis]]></image:caption>

I submitted an initial PR to see if I’m even heading in the right direction in terms of how things should be coded in Hugo. I still feel like I don’t know the Hugo flow at all, so feel free to correct me or point me in the right direction.

Any other feedback on these standards and/or my PR?