Making {{ghost_head}} better
Know how I'm always griping about ghost_head being a monolith? I did something about it!
Introducing new options for {{ghost_head}}!
Those of you who've been reading for a while may have noticed that I'm bothered by {{ghost_head}}. I mean, it does a ton of awesome and useful stuff, but the fact that it's one beast of a monolith means that it's also a barrier to being able to do other interesting and useful things.
I did one client job that needed {{ghost_head}} replaced, and wished for the ability to adjust it without needing to completely omit it.
A year passed, and then, after some consultation with the Ghost team, I wrote it!
(Actually, I wrote it twice, because the first time, what I thought we'd agreed on didn't really work very well, and then I got carried and built a bunch of added flags that weren't actually wanted. So yeah. Coding.)
I'm super excited about this, because it's going to give anyone with the ability to load a custom theme super powers. You can load a custom version of Portal. You can replace sodo-search with a custom-built search that uses Algolia as the back end. You can not load the card assets on the front page if you don't have full post content on the front page. You can load customized metadata and schema.
All the things, and all available to anyone who can upload a custom theme.
Mini-docs:
Key | What it excludes | Additional Info/Why you should or shouldn't. |
(empty array) | No changes to current behavior | |
search | The built-in sodo-search script | Includes adding the click event listener on buttons, generating the search index, and the UI. |
portal | The portal script | Handles sign-in and sign-up, payments, tips, memberships, etc, and all the portal data-attributes. |
announcement | The announcement bar javascript | If you'd like to use the announcement bar admin settings but not have it mess up your CLS metric, this is for you. |
metadata | Skips HTML tags for meta description, favicon, canonical url, robots, referrer | Important for SEO |
schema | The LD+JSON schema | Important for SEO |
card_assets | Loads cards.min.css and .js | Needed on any page with a post body, unless your theme replaces them all. Assets can also be selectively loaded with the card_assets override |
comment_counts | Loads the comment_counts helper | Needed if the page is using {{comments}} or data-ghost-comment-count attribute |
social_data | Produces the og: and twitter: attributes for social media sharing and previews | Required for good social media cards |
cta_styles | The call to action (CTA) styles written out inline in the head of each page | Used for member signup and CTA cards - may be overwritten by your theme already |
Examples
Example: Don't load card_assets on pages that don't display a post body. (Theme-dependent.)
{{#is "home, tag, author"}}
{{ghost_head exclude="card_assets"}}
{{else}}
{{ghost_head}}
{{/if}}
Example: Give only paying members access to your custom search application (included from "custom-algolia-integration"). Write out a different schema for posts with the #recipe tag.
{{#if @member.paid}}
{{#has tag="#recipe}}
{{ghost_head exclude="schema,search"}}
{{> custom-schema-partial}}
{{else}}
{{ghost_head exclude="search"}}
{{/has}}
{{> custom-algolia-integration}}
{{else}}
{{#has tag="#recipe"}}
{{ghost_head exclude="schema"}}
{{> custom-schema-partial}}
{{else}}
{{ghost_head}}
{{/has}}
{{/if}}
Example: Load a different version of the portal.
{{!-- load a different version of portal & search depending on the page the user is on --}}
{{#has tag="#de"}}
{{ghost_head exclude="portal,search"}}
<script defer src="https://cdn.jsdelivr.net/ghost/portal@2/umd/portal.min.js"
data-i18n="true"
data-ghost="{{@site.url}}"
data-key="{{content_api_key}}" data-api="{{content_api_url}}"
data-locale="de" crossorigin="anonymous">
</script>
<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search/umd/sodo-search.min.js"
data-key="{{content_api_key}}" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search/umd/main.css"
data-sodo-search="{{@site.url}}" data-locale="de" crossorigin="anonymous">
</script>
{{else}}
{{ghost_head}}
{{/has}}
Example: Load comment counts only on pages that display them. (Will vary with theme.)
{{#is "page, post"}}
{{ghost_head exclude="comment_counts"}}
{{else}}
{{ghost_head}}
{{/is}}
Example: Load CTA assets only in situations that need them.
{{#is "post, page"}}
{{#if @member.paid}}
{{ghost_head excludes="cta_styles"}}
{{else}}
{{ghost_head}}
{{/if}}
{{else}}
{{ghost_head excludes="cta_styles"}}
{{/is}}
Example: Build the CTA styles (or their replacements) into the theme's styles.min.css so that they get loaded once and then cached by the browser.
{{ghost_head exclude="cta_styles"}}
Example: Pages about your book need og:type
of book
instead of article
{{#is "page"}}
{{#has tag="#book"}}
{{ghost_head exclude="social_data"}}
<meta property="og:type" content="book">
<meta property= ...>
{{!-- note: the theme creator will want to replace all metadata, not just this one item. --}}
{{else}}
{{ghost_head}}
{{/has}}
{{/is}}
Note: Replacing social_data or schema is a significant task. Theme creators may want to look at how {{ghost_head}}
generates this content.
Technical bits, for those curious:
How hard was it to do? Here's my lines added/removed:
OK, it wasn't actually quite as bad as it looks, since a lot of it is snapshots and new tests. It took longer to write the tests than the original feature, for sure. (Testing revealed a bug that would have been a problem for Ghost Pro users, so it was all worth it. I think...)
I'm super excited to see what you build with this. Drop me a comment below, especially if you're now loading a custom version of Portal, in pink!
Would you like to sponsor more work?