How to associate CSS to a specific partial and render it in <head> section of the HTML

Long form of the question: Is there any way to associate CSS (and why not JS) with a specific partial and ensure it’s included in the <head> (or before the body end tag for scripts) html section only when that partial is used?

The story behind that question
Initially I tried to accomplish this by using blocks. The idea was to create a block in the <head> section of my baseof.html file like {{ block "my-partial-specific-style" . }} and then define that block in my partial like so:

{{ define "my-partial-specific-style" }}
<style>
...css here
</style>
{{ end }}

...partial code

It doesn’t work for partials. It may be used for other page templates but in my case all “partial specific” blocks are de facto defined regardless of whether the corresponding partial is called or not from inside the given template/layout file. I read somewhere in the forum that blocks are not intended to be used inside partials.

Then I thought about using conditional logic with scratch variables. This time the idea was to set a scratch variable (think of it as a flag) from within a partial template like so:

{{ .Scratch.Set "useMyPartial" true }}
...partial code

Remember that this inevitably involves the passing of a “Scratch” param or the whole page context, meaning that I had to call my partial like {{ partial "myPartial" (dict "Scratch" .Scratch "otherParam" $otherParam }} or {{ partial "myPartial" (dict "context" . "otherParam" $otherParam }} to be able to set scratch variables from within the partial.

However, when I tried to get that .Scratch variable from within the <head> section of my baseof.html and then apply some conditional logic to render the desired “partial specific” inline css:

{{ if (.Scratch.Get "useMyPartial") }}
<style>
...css here
</style>
{{ end }}

Guess what happened? It didn’t work either! The problem this time was with the timing of setting and getting the scratch variable. I just tried to get something which is not set because obviously I want my partial (with the set function) to be rendered after the section (with the get function). So, the scratch variables didn’t do the trick…

And now I am stuck. The only “solution” I found is to use front matter parameters to list all the css needed for example .css file names and then render those files with <link> attributes or however you’d like in the baseof.html.

But that is not what would have been very nice to be and namely a system where partials have associated stylesheets/scripts which render in the <head> (for the stylesheets) and just before the body end tag (for the scripts) dynamically only when the partial is called.

Is there any other (even a bit) more convenient (than the front matter one) way of doing something similar? I just want to use partials in a modular and structured way by loading the css and javascript they need to work and look pretty only when they are used in the given page template…

Thanks in advance for the patience to read the whole story and my question. Hope someone share a good practice or approach for tackling the challenge!

Actually with scratch variables I found a way to track partial invocation count. Example code in my partial:

{{ $count := .Scratch.Get "partialInvocationCount" | default 0 }}
{{ .Scratch.Set "partialInvocationCount" (add $count 1) }}

If we apply some conditional logic it would be possible that way to render inline styles for a partial only when it is called for the first time (meaning there’ll be no duplication of css) but the problem is that styles will still not render inside the <head> html section which is the ultimate goal…

I don’t know of way to accomplish this. If you wanted to add css/js at the bottom of the baseof.html you can use.Page.Store to set a value within a partial. But checking the Store value at the top of the baseof.html template is nonsensical because the partials haven’t been called yet.

With the built-in conditionals I can hardcode the dependencies. For example I can call partials serving purely partial specific styling purposes (that is to render inline CSS or stylesheet <link>). Example:

<!-- layouts/_default/baseof.html -->
...
<head>
...
{{ if (eq .Section "section-with-my-partial") }}
  {{ if .IsSection }}
    {{ partial "my-partial-css" }}
    {{ partial "styles-for-another-partial-present-in-that-list-page" }}
  {{ end }}
  {{ if .IsPage }}
    {{ partial "my-partial-css" }}
    {{ partial "styles-for-another-partial-present-in-that-single-page" }}
  {{ end }}
{{ end }}
...
</head>
...

or with links:

<!-- layouts/_default/baseof.html -->
...
<head>
...
{{ if (eq .Section "section-with-my-partial") }}
  {{ if .IsSection }}
    <link rel="stylesheet" href="my-partial">
    <link rel="stylesheet" href="another-partial-present-in-that-list-page">
  {{ end }}
  {{ if .IsPage }}
    <link rel="stylesheet" href="my-partial">
    <link rel="stylesheet" href="another-partial-present-in-that-single-page">
  {{ end }}
{{ end }}
...
</head>
...

This way I can target a desired layout/template file which I know uses this or that partial and render its (of the partial/s) corresponding CSS conditionally.

The problem is that this method implies very much conditional logic (a pretty large unmaintainable bunch of nested if statements) in the baseof.html <head> section and even worse it requires a bunch of partials (mirroring those storing the markup) only with stylization purposes as soon as I want to use inline styles.

If only I could have had the possibility to write all the markup (HTML), styling (CSS) and JS for a partial in one place (template file) while everything renders where it is most optimal (HTML where it is called, CSS in the head and JS before the body end), it would be wonderful…

You might, if applicable for you, use javascript to dynamically set the styles.

I’m not an expert in all that browser DOM javascript stuff, so just take that code as a “Proof of Concept”.

baseof
...html
<head>
   <script>
      MyStyles = new Array();
   </script>
</head>
...
... at the end of body ...
  <script>
      let stylesheet = new CSSStyleSheet();
      MyStyles.forEach(style => { stylesheet.insertRule(style); });
      document.adoptedStyleSheets = [stylesheet]
   </script>
styleX.partial

maybe create a parameterized variant to specify the style you want to load or set…

{{- with resources.Get "css/styleX.css" }}
   <script>
      MyStyles.push("{{ .Content }}")
   </script>
{{- end }}

Now you can include that partial in any other template or partial and it will be applied later.

as I said - it’s a POC - no check for duplications, bad code, flickr …

The problem (I think) is that when we introduce the use of JavaScript for such purposes we’re missing the point of the whole thing which is purely about performance.

Even at the cost of a longer generation process due to some more complex templating I’d like to have a final public directory with pages where only needed styles are present and are present specifically in the <head>.

I don’t want JavaScript to do the magic as it is going to hit performance and actually outsources the problem to the front-end field i.e. the users. My only goal is to have CSS in <head> only when it is needed (that is, depending on the partial usage if possible). If it is not possible to detect the partial usage from the baseof.html which is the only template file (I can think of) with direct access to the <head> html section of every single, list, taxonomy etc. page using that template, I’ll have to explicitly call the styles for each template page somehow…

It would be great if it was just provided that partial templates could be associated with their own styles to be rendered in the <head>

Means for me there is no way with the templates only.

The js would work but pretty is different…i agree

So next in my mind would be to place some placeholders in the generated pages and postprocess the result html files injcecting the stylesheets and removing the placeholders.

I don’t understand how JavaScript is more “front-end” than CSS.
What is the size of the different CSS? Are we talking 100s of KB or just some lines here and there? Do you have any indication that CSS is a bottleneck?

To place CSS in <head> is one of the best things we can make to address the FCP (First Contentful Paint) indicator seen in google page speed insights. To use JavaScript to place CSS in <head> is literally absurd if we want to address this FCP indicator or just render something properly styled on the page as fast as possible.

Some of my partials need very few lines of “partial specific” CSS while others need some more, but cumulatively the usage of multiple partials each with its own CSS needed on one page we cannot say those are some lines here and there and I would like those hundreds of lines to be rendered (like critical inline CSS) or linked in the <head> for maximum performance of the finally generated html pages…

Well, does Lighthouse tell you that you have a performance issue with CSS? Or are you trying to solve a non-existent problem?

If you bring a working implementation of that idea, it would be nice? And if it is possible to place the partial specific CSS inside the partial html in a way that the post process involves association of those placeholders with the partials and the CSS styles inside them, it would be even nicer. But I don’t know in what language and have no idea how to accomplish this…

I am trying to use Hugo to generate the most efficient pages possible - like if I have coded each of them from scratch. If I had coded each of my pages from scratch I would have put only the needed CSS in <head>, wouldn’t you do the same?

I wouldn’t, indeed. It’s pointless to optimize things not requiring optimization. I’d work on my image sizes, caching, whatever. But in any case, I’d see where the bottlenecks are first. If Lightouse tells me I have 100 points already with the CSS whatever it is, I’m not going to get 101 points if I make the CSS any better.

And perhaps there are ways to write efficient CSS so that you don’t have to think so much about which partial needs what but rather have the relevant styles and selectors in place for all the partials.

I wouldn’t use accordion styles in a page where there is no single accordion html element. I don’t want to load partial specific script where it is not needed (for example a script for toggling “expanded” html attribute accordion items expandable containers).

To have clean and modular code is not “pointless optimization” - I would appreciate a streamlined way of associating CSS to partial and render it in <head>. The current situation is that we can only generate modular markup (by using partials) but this modular type of thinking implies that styling (CSS) and functionality (JS) should also be separated from the base styling and scripts, and not everything should be loaded into a common giant CSS for every little page…

A compromise “solution” I came to is to define a block created in baseof.html for each template layout like so:

<!-- layouts/_default/baseof.html -->
...
<head>
...
{{ block "page-specific-styles" . }} {{ end }}
...
</head>
...

And then define it for each page template. The defined block should contain page specific CSS with links or inline styles for each partial used inside the given page template. This way I define the block inside the corresponding page template and not in any partial template (which is not possible).

To avoid duplication I should consider using <link> for styling or another bunch of partials only with <style>...some styles</style> in them…

PS: hardcoded but should do the job

before all that next I have to say

I would not go with external postprocessing unless theres tons of pages each with different huge css files.

real bare POC to show something

  • standard hugo base theme
  • shortcode to include styles
  • styles in assets/css
  • add target placeholder to baseof
  • some new partials called to show how it works
  • add “PowerShell (Core)” postprocess script
git clone https://github.com/irkode/hugo-forum.git topic-52293 --single-branch -b topic-52293
cd topic-52293
hugo
.\replace.ps1

check your public folder and compare *.html with *.html.styles

POC:

  • no error handling
  • code is hacky
  • no optimization
  • html is ugly before calling replace

UPDATE: still a POC :wink:
latest commit

  • inlines the css wher the addstyle is called
  • added: replace-styles.ps1
    • removes the inlined styles
    • adds styles .css files to head

this way the sie should display with hugo server and the sequence hugo;.\replace-style.ps1 creates the publishable content.

1 Like

I forgot about the templates.Defer function introduced in v0.128.0.

For example, in the base template:

<head>
  ...
  {{ $data := (dict "page" .) }}
  {{ with (templates.Defer (dict "data" $data)) }}
    {{ if page.Store.Get "hasMath" }}
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
    {{ end }}
  {{ end }}
  ...
</head>

Here’s a contrived and convoluted example site:

git clone --single-branch -b hugo-forum-topic-52293 https://github.com/jmooring/hugo-testing hugo-forum-topic-52293
cd hugo-forum-topic-52293
hugo server

From the console warnings you can see that the deferred value of .Page.Store.Get "hasMath" is only true when rendering content/posts/post-3.md.

Other than test site above I have no experience with templates.Defer.


And remember that you can stuff anything into a page Store, including CSS rules, so you could have your CSS rules within a style element with the head element instead of referencing an external style sheet.

2 Likes

Wow, @jmooring that’s cool stuff again

cloned it and played around: quite hacky code, but may work as a startoff how to inject stuff to head (or somewhere else :wink:

  • partials/addStyle.html

    add css to the page (just store it here)

    not sure if we could use global page function here (list pages, pagination) so I passed the current page and the url in a slice

    param 1: page object
    param 2: path to css in assets

    called as: {{- partial "addStyle.html" (slice $ "css/style-footer.css") -}} in other templates

    partial code:

    {{ $page := index . 0 }}
    {{ $style := index . 1 }}
    {{ $styles := $page.Store.Get "styles" }}
    {{ if not (in $styles $style) }}
       {{ warnf "ADD: %s to %s " $style $page }}
       {{ $page.Store.Add "styles" (slice $style) }}
    {{ end }}
    
  • partials/renderStyle.html

    called as partialCached (key = css file) to avoid loading the resource multiple times

    {{ $link := "" }}
    {{ warnf "RESOURCE: %s" . }}
    {{- with resources.Get . -}}
      {{ $link = .RelPermalink }}
    {{- end }}
    {{ return $link }}
    
  • finally baseof.html

     <head>
        {{ partial "head.html" . }}
        {{ $data := (dict "page" .) }}
        {{ with (templates.Defer (dict "data" $data)) }}
           {{ range page.Store.Get "styles" }}
              {{ warnf "DEFER: add %s to page %s " . page }}
              <link rel="stylesheet" ref="{{ partialCached "renderStyle.html" . . }}" />
           {{ end }}
        {{ end }}
     </head>
    

p.s. my repo-branch shown above has been updated with this version.

1 Like

Sounds amazing! I’ll definitely try to use that function to create a working solution to associate a style to a partial. Coming soon… :slight_smile:

Thank you @irkode for the further development of what @jmooring offered as the origin of my solution! For all new joiners to the discussion definitely check their latest suggestions and responses:

I’ll offer a concrete implementation which suits me best for now and mark it as a “Solution” not for narcissistic reasons, but simply because otherwise I can’t pinpoint exactly which one of you gave me the answer itself - the whole discussion, which I’m very grateful for, was helpful for that!

In baseof.html:

<!-- layouts/_default/baseof.html -->
...
<head>
  ...
  <!-- Custom styles "hook" for partial specific CSS -->
  {{ $data := (dict "page" .) }}
  {{ with (templates.Defer (dict "data" $data)) }}
      <style type="text/css">
          {{ page.Store.Get "myPartial-style" | safeCSS }}
      </style>
  {{ end }}
  ...
</head>
...

In my partial template:

<!-- layouts/partials/myPartial.html -->
{{ $page := .page }}

{{ $page.Store.Set "myPartial-style" `
    body {
        background-color: red !important;
    }
    ... some more meaningful CSS rules
` }}

...partial markup and template code

Voilà! This is a very sketchy and prone to future development solution

for example you could add to instead of setting a fixed value of page.Store; soffisticate the baseof.html to range through all styles, added inside partials; link to .css files inside the assets folder instead of directly inserting CSS rules and/or whatever you want

…but the important thing is that Hugo functionally provides this possibility to associate in such a way styles to a given partial, which we can then render above in the head for better performance of the page! The idea of @irkode is very interesting, I just wanned to keep everything in as less files as possible (that’s the reason of passing CSS directly via page.Store, hinted by @jmooring, which is actually possible and cool).

PS: I’m impressed with how fast Hugo is developing. Considering that this summer I downloaded the “latest” version (0.125…) and played around with it and got to a point where something a little more specific and particular seemed unachievable (my question in this discussion), it is very impressive how the new version added very shortly after (0.128) actually enabled a solution to the problem. I hope it continues to develop at such a pace in the future!

1 Like