const response = await fetch('/api/posts', { signal: AbortSignal.timeout(5000) })
That's it. When the five seconds pass, fetch rejects with a TimeoutError. It's in every browser since 2022 and in Node 17.3+.
If you also want to cancel on your own (the user navigated away, say), combine signals:
const controller = new AbortController()
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(5000)])
fetch('/api/posts', { signal })
controller.abort()