diff --git a/docs/guides/best-practices.md b/docs/guides/best-practices.md index 48eb86eb..cccd045a 100644 --- a/docs/guides/best-practices.md +++ b/docs/guides/best-practices.md @@ -141,3 +141,85 @@ const client = hc('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