Generate Unique IDs Per Request in Astro
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.