Generate Unique IDs Per Request in Astro

Originally Published: August 11, 2024

In a recent Astro.js project, I had a Form component that could be used multiple times on the same page. Each form had labels and inputs that needed unique but matching id attributes.

Inspired by Alpine.js’s $id magic function, I came up with a solution to do the same when doing server rendering in Astro.

My solution works by injecting a new instance of an ID generator into Astro.locals inside the onRequest hook. This function is called once per request, before the page is rendered, which gives us a unique ID generator for each request.

First, let’s add the type definition for the function:

src/env.d.ts:

declare namespace App {
  interface Locals {
    $id: (prefix: string) => string
  }
}

Then, in src/middleware.ts, we’ll add this function in the onRequest hook:

import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.$id = (() => {
    // The map to store how many times each prefix has been used in this request.
    const map = new Map<string, number>()
    return (prefix: string) => {
      const count = (map.get(prefix) ?? 0) + 1
      map.set(prefix, count)
      return `${prefix}-${count}`
    }
  })()

  return await next()
})

Now, in our forms, we can call Astro.locals.$id() with the id prefix we want.

---
const { $id } = Astro.locals
const nameId = $id('name')
---

<form>
  <label for={nameId}>Name</label>
  <input id={nameId} />
</form>

When this form is rendered for the first time in a request, nameId will be "name-1". On the second render of the component in the same request, it will be "name-2", and so on. Every request gets its own unique IDs starting from 1.