Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions docs/guides/best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,85 @@ const client = hc<AppType>('http://localhost') // Typed correctly
```

For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications).

## HEAD Request Best Practices

### Understanding Hono's HEAD Handling

Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body. This behavior is built into the framework's dispatch layer and happens before route matching occurs.

### ✅ Do: Use GET Routes for HEAD Requests

```typescript
// GOOD: This GET route automatically handles HEAD requests
app.get('/api/users', async (c) => {
const users = await getUsers()
c.header('X-Total-Count', users.length.toString())
return c.json(users)
})

// HEAD /api/users will return:
// - Same headers as GET (including X-Total-Count)
// - Status 200
// - No body (null)
```

### ✅ Do: Use Middleware for HEAD-Specific Logic

```typescript
// GOOD: Use middleware when HEAD needs different behavior
app.use('/api/resource', async (c, next) => {
await next()

// Add HEAD-specific headers after the handler
if (c.req.method === 'HEAD') {
c.header('X-HEAD-Processed', 'true')
// Don't compute expensive body content for HEAD
c.res = new Response(null, c.res)
}
})
```

### ❌ Don't: Try to Create Dedicated HEAD Handlers

```typescript
// BAD: This won't work as expected
app.head('/api/users', (c) => {
// This handler will NEVER be called
c.header('X-Custom', 'value')
return c.text('ignored')
})

// BAD: Using on() also won't work
app.on('HEAD', '/api/users', (c) => {
// Still converted to GET before route matching
})
```

### Performance Considerations

- **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation
- **Cache headers work identically**: HEAD responses respect the same caching rules as GET
- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests

### Testing HEAD Requests

```typescript
// Always test both GET and HEAD responses
it('handles HEAD requests correctly', async () => {
const getRes = await app.request('/api/users')
const headRes = await app.request('/api/users', { method: 'HEAD' })

expect(headRes.status).toBe(getRes.status)
expect(headRes.headers.get('X-Total-Count')).toBe(
getRes.headers.get('X-Total-Count')
)
expect(headRes.body).toBe(null)
})
```

### Notes

- The automatic HEAD conversion ensures consistent headers between GET and HEAD responses
- This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js)
- If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling
Loading