Since I’ve been working on Swatcher, I’ve struggled with the idea of SEO. From my experience, Google does crawl pages on a SPA successfully, but occasionally I find it won’t capture some meta tags. Perhaps that’s due to a delay in rendering when we update the meta tags on the client side. I also don’t know how other search engines handle SPAs.

With that in mind, I wanted to provide the proper meta tags ahead of time, and potentially open the door to something closer to server-side rendering.

# Edge Functions

With Netlify, we have access to Edge Functions. These are distributed globally and help ensure that users from anywhere in the world receive a quick response.

In my case, I wanted to inject meta tags for paint information. So to start, I created a function in netlify/edge-functions/paint.ts.

Next, in netlify.toml, I added the following to ensure any paint detail pages get routed to this function:

[[edge_functions]]
  function = "paint"
  path = "/paint/*"

For easier modification of HTML, I installed cheerio.

With those in place, here’s a basic idea of how the function looks:

import * as cheerio from 'cheerio';

export default async (request: Request) => {
  try {
    const url = new URL(request.url);
    const paintId = url.pathname.split('/').pop();

    if (!paintId) {
      return new Response('Bad Request...', {
        status: 400,
      });
    }

    const paint = /* get paint details */

    if (!paint) {
      return new Response(
        'Not Found...',
        {
          status: 404,
        },
      );
    }

    const baseUrl = `${url.protocol}//${url.host}/index.html`;

    const htmlResponse = await fetch(baseUrl);
    const html = await htmlResponse.text();
    const $ = cheerio.load(html);

    const title = `${paint.name} by ${paint.brand} | Swatcher`;
    const description = `Explore ${paint.name} by ${paint.brand}...`;
    const themeColor = paint.hex;

    const themeMetaTag = `<meta name="theme-color" content="${themeColor}" />`;
    $('head').find('meta[name="theme-color"]').remove();
    $('head').append(themeMetaTag);

    const titleTag = `<title>${title}</title>`;
    $('head').find('title').remove();
    $('head').append(titleTag);

    const metaTag = `<meta name="description" content="${description}" />`;
    $('head').find('meta[name="description"]').remove();
    $('head').append(metaTag);

    return new Response($.html(), {
      status: 200,
      headers: {
        'Content-Type': 'text/html',
      },
    });
  } catch (error) {
    return new Response(
      `Edge function error: ${error instanceof Error ? error.message : 'Unknown error'}`,
      {
        status: 500,
        headers: {
          'Content-Type': 'text/plain',
        },
      },
    );
  }
};

Once deployed, and I visit a paint detail page, I can see that the HTML now contains the desired meta tags from the server.

# Going Further

This is a simple example, but you could go further by making any data requests here as well. You can inject a script into your HTML with pre-fetched data, allowing the page to appear to load instantly.