From Hugo in Action by Atishay Jain

This article is all about adding LaTeX rendering to a static website built with Hugo.

Take 40% off Hugo in Action by entering fccjain into the discount code box at checkout at

For the intro to this article, check out part 1.

Deploying to Heroku

In case you aren’t using Netlify, you need to select a vendor to host the APIs. Many cloud vendors support both the PAAS and the FAAS models and we’re free to decide what approach to take to deploy our website. Because Netlify uses the FAAS solution, we demonstrate a PAAS solution. Heroku is used as the PAAS platform, though we use little of its features and therefore the code should work in any PAAS platform we choose.

Heroku has integrations with both the Node.js ecosystem and GitHub making the deployment job for us easy. Heroku also provides continuous integration and deployment for our code without us having to write anything in our GitHub actions which makes our job easier. Once configured, we need to push the code to GitHub to get it live within Heroku.

  • Once you sign up into Heroku, you’ll land into where you can click on New > Create new app.

Figure 7. Creating a new application in Heroku to host our APIs

  • Next, we need to give the app a name. App names are unique in Heroku. latex2svg has been reserved for this article.

Figure 8. Giving a name to an app in Heroku

  • We can now decide the mode of integration. Direct GitHub integration is the easiest and we use that for our use case. To connect to GitHub, you can go to Deploy > Deployment method > GitHub and then click on Connect to GitHub

Figure 9. Connecting Heroku to GitHub

  • After providing credentials, we can search for repositories that we want to connect to Heroku, select the correct repository and then click connect.

Figure 10. Finding and connecting the right repository from GitHub to Heroku

  • Before triggering the deployment, we need to go to the Settings tab and in the Buildpacks section select node.js. Because we have both go.sum and package.json, Heroku can get confused.

Figure 11. The settings tab of Heroku contains Buildpacks which is the configuration for the programming platform for Heroku.

We need the node.js Buildpack for our APIs.

Figure 12. Heroku supports multiple platforms to deploy our applications. Node.js is one of them which is needed for getting our MathJax code up and running.

  • In the settings there’s also a config vars section where we can define our environment variables. We need to define LATEX2SVG_PASSWORD for getting a password to restrict access.

Figure 13. Config vars in Hugo are passed as environment variables to the running code.

  • Once the build settings are setup we can trigger the deployment from Deploy > Manual Deploy. We should first do a manual deploy for verification that everything is correct before switching on automatic.

Figure 14. Manual and automatic deployments in Heroku. Manual deploys should be used for verification and once we know everything is working, we can go back to automatic.

After the code goes live, we can call https://<endpoint><password> to get the same response that we got previously when we ran locally.

Figure 15. When we trigger manual deployment in Heroku, step by step progress is provided until everything is ready.

We can troubleshoot any issues looking at the application logs which are provided in the More menu on the top right where we can click on View logs

Figure 16. Viewing logs on Heroku. Heroku provides access to machine logs and build logs separately and we can get into them to figure out issues.

Figure 17. Sample logs from the node.js process running in Heroku. We can use console.log to get all logs from node.js. All crashes and error logs are also available.

Monorepo vs separate repos for APIs and Markup

We decided to use the repository that was used for the website for the API code as well. This has a clear disadvantage of pushing the needless markup code to Heroku and rebuilding the Heroku based APIs every time a markup-based document has changes. If this turns into a big problem, we can choose to have a separate branch for the API or a separate repository. We can also change our integration to happen via GitHub actions instead of the direct Heroku import where we can build the smartness to recognize whether the API has changed. We can move to manual deploys if the APIs aren’t changing frequently.

The choice of a mono-repo has nothing to do with Hugo or the Jamstack. It’s a matter of personal preference. Netlify works a lot better with one repository and managing changes across APIs and their invocation in the core template code may be easier if using a monorepo.

Creating shortcode to render LaTeX

With a functional API, the major work needed to get to rendering LaTeX on the website is done. We need to invoke it and then render the result. Now we need to create a shortcode to call the API and display the result in the Hugo based website.

Although we use the APIs at compile time, if we want to use them at runtime, the steps to setup the APIs are exactly the same. Instead of calling the API via Hugo, we use the fetch function.

In the shortcode, we take the LaTeX code as the inner content of the shortcode and then call the latex2svg API to get the SVG version of this LaTeX document that we can convert to a resource using resources.FromString or render inline as an SVG document. We’re rendering the SVG inline for our use case.

Although we can use the site.baseURL in the endpoint in case we use Netlify, it doesn’t work for compile time API access when the website has never been pushed live before. This causes the need to have a deployment twice – once to get the function up and the second time to call it. Unless calling from JavaScript, it is advisable to have a fixed URL for the API present in the config.

The other parameters of the API can be converted into arguments for the shortcode. We use the default values as supplied in the official MathJax documentation at

The corresponding file is also present in the resources (

 {{/* layouts/shortcodes/latex.html */}}
 {{/* layouts/shortcodes/latex.html */}}
 {{/* defaults taken from
 {{/*  a Boolean specifying whether the math is in display-mode or inline mode */
 {{ $display := true }} 
 {{ with .Get "display" }}
 {{ $display = . }}
 {{ end }}
 {{/*  a number giving the number of pixels in an em for the surrounding font. */
 {{ $em := 8 }}
 {{ with .Get "em" }}
 {{ $em = . }}
 {{ end }}
 {{/*  a number giving the number of pixels in an em for the surrounding font. */
 {{ $ex := 16 }}
 {{ with .Get "ex" }}
 {{ $ex = . }}
 {{ end}}
 {{/*  a number giving the width of the container, in pixels. */}}
 {{ $containerWidth := (mul 80 $ex) }}
 {{ with .Get "containerWidth" }}
 {{ $containerWidth = . }}
 {{ end }}
 {{/*  a number giving the line-breaking width in em units. Default is a very large number (100000), so effectively no line breaking. */}}
 {{ $lineWidth := 100000 }}
 {{ with .Get "lineWidth" }}
 {{ $lineWidth = . }}
 {{ end }}
 {{/*  a number giving a scaling factor to apply to the resulting conversion. Default is 1. */}}
 {{ $scale := 1 }}
 {{ with .Get "scale" }}
 {{ $scale = . }}
 {{ end }}
 {{ if (and $.Site.Params.Latex2Svg (getenv "LATEX2SVG_PASSWORD") ) }} 
   {{ $json := getJSON $.Site.Params.Latex2Svg "?" (querify "tex" .Inner) "&password="
 (getenv "LATEX2SVG_PASSWORD") "&display=" $display "&em=" $em "&ex=" $ex
 "&containerWidth=" $containerWidth "&lineWidth=" $lineWidth "&scale=" $scale}}
   {{ with $ }}
   {{. | safeHTML}} 
 {{ end }}
 {{ end }}

Setup all parameters with meaningful defaults

Disallow calling the server if there’s no LATEX2SVG_PASSWORD or the endpoint isn’t defined.

Embed the data without escaping the SVG content inline.

Next, we need to enter the Latex2Svg endpoint in the website config, which depends on whether you used Heroku or Netlify for deployment.

 # config/_default/params.yaml
 Latex2Svg: https://<endpoint>
 # or
 Latex2Svg: https://<endpoint>/.netlify/functions/latex2svg

If we don’t want to call into the cloud service, we can use the http://localhost:3000/latex2svg as the URL inside of config/development/params.yaml. We can also disable the call during development by setting this to blank.

Adding some Latex in our website

With all the hard work done to get a shortcode to render latex, the next step is to write some LaTeX in our website. In our blog we’ve a page on triangles.

Let’s add the formula for the area of a triangle in there:

 <!-- content/blog/tropical triangles/ -->
 {{< latex>}}\text{Area} = \frac{b \times h}{2}{{</latex>}}

Let’s do the same in circle:

content/blog/community/ –>
{{< latex>}}\text{Area} = \pi r^2{{</latex>}}

We can also do complex mathematical equations like the mathematical definition of a Bézier curve.

 {{< latex>}}
 <!--  content/blog/community/ -->
 \mathbf {B} (t)=\sum _{i=0}^{n}{n \choose i}(1-t)^{n-i}t^{i}\mathbf {P} _{i}
 {{</ latex>}}

We can do inline LaTeX as well.

 <!--  content/blog/community/ -->
 The area of a circle is {{<latex display="false">}} \pi r^2 {{</latex>}}

Figure 18. LaTeX rendered as SVG in the curve web page of the website. Using compile time API access, we can convert LaTeX to SVG without requiring any JavaScript on the client.

Code             Checkpoint.         Live       at      Source    code      at

That’s all for this article. If you want to learn more about the book, check it out on Manning’s liveBook platform here.