Is it possible to have the effect of a single content type having different archetypes?

I feel like I might be missing something basic because I have what I think is a common use case that I’m not sure how to solve.

Imagine having a “blog” content type of which you have a “blog” layout. Everything is good so far but now you have a type of blog post called a “tip”. Tips have the same exact layout and URL paths as a regular blog post, the only difference is a few frontmatter properties which in turn render certain elements differently (a different newsletter or blog hero image, etc.).

In Jekyll this was really easy, I made a tip layout which derived from layout: "blog" and then I set my custom frontmatter there since Jekyll lets you set frontmatter in a layout. Then in every tip blog post I set layout: "tip" and everything worked.

In Hugo I don’t know how to solve this. You can’t seem to set frontmatter in layouts and while archetypes exist, they seem coupled to the file system since you can’t set archetype: "tip" in a post’s frontmatter to select a different one. I don’t want a content/tip directory since in my mind a tip belongs in content/blog since it’s effectively a type of blog post.

How do you handle this use case where you have 1 content type that needs to be slightly customized with frontmatter?

I have over 100 tips and want to avoid explicitly setting the common tip related frontmatter properties in each one. I also want to avoid adding a bunch of custom tip related logic in my base layout with conditions around checking if it’s a tip based on the file name of the post, etc…

Have a look at

Then code your output in the tip layout or use a tip datafile

How would I do this? I’d like the tip layout to be the same as the blog layout, but I don’t want to copy / paste the blog layout into the tip layout or use a partial for this. Using a partial here wouldn’t help as well because the real change happens in the baseof.html layout.

My layouts/blog/single.html layout looks like this:

{{ define "main" }}
  # REDACTING 60 LINES OF TEMPLATE LOGIC FOR BREVITY
{{ end }}

I couldn’t figure out how to do the effect of this in layouts/tip/single.html:

---
type: "blog"

params:
  card: "my-custom-thing"
  newsletter: "some-other-custom-thing"
---

The above is what Jekyll allowed to do but Hugo doesn’t let you set frontmatter in a layout?

If i get it right jekyll merges the frontmatter from the layout with the one from the page

But it wont get magically to the page right. So there is some coding evaluationg the values

Then the datafile would be the option. Use that as frontmatter values and adjust the printing to html stuff

It does but it also supports layouts having their own frontmatter.

In my Jekyll set up I have a few layouts:

  • default
    • Comparable to layouts/_default/baseof.html in Hugo
  • blog
    • Comparable to layouts/blog/single.html in Hugo
  • tip
    • Nothing exists yet in Hugo for this

The entire tip layout (layouts/tip.html) in Jekyll is this:

---
layout: "blog"
newsletter: "intro-to-docker"
---

{{ content }}

Then in each of my tip related posts I set layout: "tip" in its frontmatter. That in turn renders the tip layout which really renders the blog layout and now everything works without any duplication or custom template logic like “if tip…”.

That’s the behavior I’m trying to replicate in Hugo. The implementation doesn’t matter, I’m open to changing how I think about the problem. I just want to define a custom set of tip specific frontmatter in 1 spot that gets applied to all tip blog posts.

  • having the same template code in two templates the Hugo way would be to use a partial
  • If there’s some part that is different in the resulting pages it must be implemented somewhere.
  • I guess you have that code adding a frontmatter value or not newsletter
  • if the “real thing” happens in baseof.html we are missing some important information

How static is that stuff?


With Hugo you would set the layout = "tip" in your tip blog posts and so a different template will be used if you define one. Maybe one would modularize up the 60 lines of template code to common and specific parts and use partials for that.

you also could defining a baseof.html having more than one block. You may also set default values for a block if it is not defined by a template. or just not define blocks

block "commmon1"
block "tip"
block "common2"

and more … many options to achieve your target page.

Coming to the variable part you stated as “slightly customized” but on the other hand “a bunch of tip related code”. Sounds a little contradictory. So we need to know what data coming from where (not how it is implemented in jekyll) static, variable and where the data should go, the exact differences for your target files.

So I’d recommend to step back from coding:
re-check your requirements especially the non-functional ones – not based on input and output but grown with your jekyll history.
rethink it the Hugo way not “replicating” jekyll architecture and way of working
→ on the long run you won’t be lucky


I understand that you want to keep the target as close to before as possible. But for the source you should really recheck – which are functional requirements, – which is just a migration –

It’s like changing the job but wanting all processes in the new company work the same.

Thanks for the reply. Let me break it down with more specifics about my set up with real code and what I’m trying to customize.

Newsletter

In my layouts/_default/baseof.html file, it has this snippet:

    {{ if .Params.Newsletter | default true }}
      {{ $partial := .Params.Newsletter | default "newsletter" }}
      {{ $newsletter := print "cta/" $partial ".html" }}
      {{ partial $newsletter . }}
    {{ end }}

All of my pages by default will display a newsletter sign up form near the bottom of the page. The newsletter being displayed can be customized depending on what you’re viewing.

Certain pages disable it, most blog posts default to a newsletter called “newsletter” but I currently have ~100 docker tips and I want them to use an “intro-to-docker” newsletter instead.

I have that set up to where the post’s frontmatter can set params.newsletter: "newsletter" which works if I explicitly set it. Technically I don’t set it most of the time because it defaults to “newsletter”. But overriding that to “intro-to-docker” for the 100+ tips is tedious.

Goal 1 is to have that param set to something else for tips in 1 spot.

Cards

Every blog post has an image. I defined this as a card because it’s both the blog post’s main hero image as well as a Twitter / open graph card.

Not all pages have a card being displayed so this component is defined in layouts/blog/single.html, here’s the code:

    {{ if .Params.card | default false }}
      {{ $cardPath := printf "/blog/cards/%s" .Params.Card }}
      {{ partial "image.html" (dict "src" $cardPath "class" "post-card") }}
    {{ end }}

All blog posts have frontmatter defined with params.card: "some-image-name.jpg".

The Docker tips all use the same image of params.card: "docker-tip.jpg".

Goal 2 is to have that param set to something else for tips in 1 spot.

Why partials might not work here

I can technically extract the ~60 lines of layouts/blog/single.html content into a partial and use it in both a layouts/blog/single.html and layouts/tip/single.html file.

But that doesn’t solve the newsletter problem since that newsletter code is defined in the baseof config?

Live demo

I don’t have my site powered by Hugo yet but my site is up with Jekyll:

the main thing for your Goals is to distinguish between a “tip” and a “post”
but you still think in document/template organisation of Jekyll and that maybe would be a migration topic.

you have your structure, where you

  1. set “layout: tip” in tips frontmatter
  2. chain the layouts
  • first tag: adds the needed attribute for tip
  • then blog: which evaluates the attribute set in tip

having (1) and layouts for tip and blog and the core blog code extracted to a partial you could call that partial with an additional parameter layout
This paramter could be used to index into a site.Params or datafile

btw any specific reason to have the newsletter in baseof? could be extracted to a partial/block aswell.

Guess the Hugo way would be to have different (source) folders , maybe filenames for identifying a tip/post and then cascade the values down in _index.md using _target/path option

btw: would maybe a nice feature to cascade by type or layout :wink:

btw any specific reason to have the newsletter in baseof? could be extracted to a partial/block aswell.

It was only a few lines of code and applied to all pages in 1 spot. I tend to only create abstractions when they are required or make things more readable.

It being a block is only beneficial here if cascading by type of layout was a feature right? Otherwise I’d have to duplicate everything or create a bunch of partials for the common parts in the blog type that also apply to the tip.

Is this possible today? I’m not sure I like having tips in a separate folder though.

I may just duplicate the tip specific frontmatter in 100+ posts because all of the other options IMO make the code base a lot more convoluted and harder to reason about.

Depends on the details. You cannot cascade templates but you address templates by frontmatter layout. And even have different basof templates depending on stuff. You may can define more than one block ins fe. Single and leave out ones you have… take a look ate template lookup order page.

Pretty sure you had a cascade example before with the drafts.
And there you liked the idea…i personally would consider a rip collection different from posts and store them separately…

Usually that would be done when addine one post after the other…later one recons theres duplication and would extract it to a supported method. And we are back to the separate folders and cascade :slight_smile:

The datafile per frontmatter value is also a common design pattern

And the idea would be to set content/tip/_index.md to draft so its index page is never rendered, similar to the drafts?

Conceptually I see them as the same thing, not separate. It’s a blog post with a specific image and newsletter form. That’s the only difference. They are also combined in with other posts in the index view.

Are there examples of this somewhere just to see how that would work?

Data basics are here: Data | Hugo

I recently answered one . Not exactly your structure but i think you are able to get into it: Extracting tags from `site.Data.$files`. (help with go-template lang)

I would rather set the build option to render=never for the index.

Moving the tips to blog/tips also an option… or different file names inside the blog folder anything that you can target with the cascade

That is a possibility. All of the tips have -docker-tip- in their file name. I didn’t know you could target that. Would that be something like path: /blog/*-docker-tip-* on a cascade _target?

That’s a good tip, I didn’t know that option existed.

Yes

You wont need that if you keep that all in blogs folder

Btw i have a concept for

  • merging centrally defined propertie
    • based on site config or data file
  • to a page based on a front matter key
  • using template files

I think I can provide a POC timely

Here it is: some code to play around with

last param wins:

  • site.Params
  • page.Params
  • /data/layout-name-from-frontmatter.yaml (if present)

this is a separate data structure and you have to access the params using the index function

baseof
<!DOCTYPE html>
<html>
  <head>
   {{ with index site.Data .Layout }}
      {{ $.Scratch.Set "params" (merge site.Params $.Params .) }}
   {{ else }}
      {{ .Scratch.Set "params" (merge site.Params $.Params) }}
   {{ end }}
  </head>
  <body>
    <main>{{ block "main" . }}{{ end }}</main>
  </body>
</html>
single or blog or... template just for visualization
{{ define "main" }}
<p>blog Scratch Params</p>
{{ $myParams := .Scratch.Get "params" }}
{{ highlight (debug.Dump $myParams) "JSON" }}

<p>blog Frontmatter Params</p>
{{ highlight (debug.Dump .Params) "JSON" }}
{{ end }}

you can play around with putting parameters and values in either of these

/data/tip.json
newsletter: docker
frontmatter of a tip
---
title: Tip 2
layout: tip
params:
  newsletter: "my newsletter"
  blog:
     card: "changed"
---
hugo.toml
[params]
   [params.blog]
      newsletter = "newsletter"
      card = "card"
  • all the .Params stuff cannot be used – use index instead
  • dont ask me about performance

defining defaults in site.cfg no need to check that later
you may even use more datafiles or sections in site.cfg
you may merfge only subkeys of the stuff

Have fun playing

1 Like