Skip to main content

Complicating the Blog

·919 words·5 mins

Complicating the Blog
#

Before I post any actually useful content, I thought I’d continue to document my development of this website in case it helps someone out at some point. I was looking at adding a bog standard “About Me”, “Contact Me” and maybe a “Resources”, where I link to some interesting websites that I value but found due to the nature of a static site, it’s a little more tricky to have a contact me form than you’d think. Now I do have the e-mail link underneath my creepy AI pic everywhere but where is the fun in that?

Had a quick look around and it seems a fun way to do this is to use an Azure Function with a Resend.com account to send a mail to me for extra complexity. This page will largely detail that as well as the “menu” component within Hugo/Blowfish theme.

Adding pages
#

The pages were actually quite simple. Hugo has built in menus that you can append pages to by completing front matter. For my pages, I simply added a menu and weight that specifies that I’d like the page to appear in the main menu and the different weights are used to order the pages:

---
title: "About Me"
description: "blah, blah, blah"
layout: "background"
date: 2026-03-07
menu: "main"
weight: 10
---

This makes them appear at the top right of all pages as you can likely see right now in the order of in ascending order from left to right. Easy.

About me
#

Looking at the shortcodes available in the Blowfish theme, they have a nice “timeline” component that I think will be nice for a live job history. I guess the About me page should be a live CV so that’s what we are going to do.

  • The syntax is available here and I simply plug it in and fill it with details to present my job history.
  • For my key skills section, I’ll ended up using lists in tabs, doesn’t look the best, will need more work.
  • Also ended up using a gallery for professional certifications.

Contact me
#

RESEND Api Key This is where we can have some fun. First of all, I headed to www.resend.com and signed up for free then pulled an API key. I went straight to the static webapp for this site in Azure and added it as an environmental variable for the site under RESEND_API_KEY.

The Azure Function Next up, we will create a small Node.js function. Azure Static Web Apps automatically detect and builds APIs if I you place them in a folder named api at the root of your repository. I create the following file in the root of the project api/src/functions/contact.js

The contents of this file will be:

const { app } = require('@azure/functions');

app.http('contact', {
    methods: ['POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        try {
            // Parse the incoming JSON from your website
            const { name, email, message } = await request.json();

            if (!email || !message) {
                return { status: 400, body: "Email and message are required." };
            }

            // Send the email via Resend's API
            const res = await fetch('https://api.resend.com/emails', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${process.env.RESEND_API_KEY}`
                },
                body: JSON.stringify({
                    from: 'Contact Form <onboarding@resend.dev>', 
                    to: 'YOUR_PERSONAL_EMAIL@DOMAIN.COM', // Change this to your actual email
                    subject: `New message from ${name}`,
                    text: `Reply to: ${email}\n\nMessage:\n${message}`
                })
            });

            if (res.ok) {
                return { status: 200, jsonBody: { success: "Message sent!" } };
            } else {
                return { status: 500, jsonBody: { error: "Failed to send via email provider." } };
            }
        } catch (error) {
            context.error(error);
            return { status: 500, jsonBody: { error: "Internal server error." } };
        }
    }
});

We also need a very basic package.json file in the actual api/ folder so Azure knows it’s a Node app. I create api/package.json and paste the following content:

{
  "name": "contact-api",
  "version": "1.0.0",
  "dependencies": {
    "@azure/functions": "^4.0.0"
  },
  "main": "src/functions/*.js"
}

The Contact Form Now within my actual contact.md file in the content directory, I can place the following to define a contact form:

<div id="form-container">
  <form id="contact-form">
    <label>Name: <input type="text" id="name" required></label><br><br>
    <label>Email: <input type="email" id="email" required></label><br><br>
    <label>Message:<br><textarea id="message" rows="5" required></textarea></label><br><br>
    <button type="submit">Send Message</button>
  </form>
  <p id="status-message"></p>
</div>

<script>
document.getElementById('contact-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const status = document.getElementById('status-message');
  status.innerText = "Sending...";

  const data = {
    name: document.getElementById('name').value,
    email: document.getElementById('email').value,
    message: document.getElementById('message').value
  };

  try {
    const response = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (response.ok) {
      status.innerText = "Thank you! Your message has been sent.";
      document.getElementById('contact-form').reset();
    } else {
      status.innerText = "Oops! Something went wrong.";
    }
  } catch (err) {
    status.innerText = "Error connecting to the server.";
  }
});
</script>

GitHub Actions Update Now we need to tell GitHub actions about the above. To do this, we open the existing workflow file in .github/workflows/deploy.yml and add an api_location property to the Deploy to Azure Static Web Apps task as follows:

      # 3. Upload the pre-built site to Azure (Bypassing Oryx)
      - name: Deploy to Azure Static Web Apps
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ steps.get_token.outputs.swa_token }}
          action: "upload"
          app_location: "public" # Point this directly to Hugo's output folder
          api_location: "api"    # Tells Azure where function code is
          skip_app_build: true   # CRITICAL: This tells Azure Oryx to back off

When we push these changes, the function will be picked up and work.

Resources page
#

I will create a stub page for Resources where I can link various links I think are interesting too.

Ben Stalker
Author
Ben Stalker
A passionate IT professional who loves all things coding and scripting