> if I open a new tab page for the first time, it's pretty slow. If I leave that NTP open and open another one, it's a lot faster.
If your extension doesn't have another tab/popup open or the background script running, opening it in a tab requires creating a new OS process for the extension, which takes at least 50ms, usually longer. In ManifestV2 the workaround was to declare a persistent background script, even empty. In ManifestV3 you can
prolong the lifetime of the service worker, but it requires broad host permissions, unfortunately. Another workaround is to fade in the UI using a CSS transition/animation. This is what Chrome's own start tab page does, and although personally I think this workaround is terrible, but I'm probably the only one as I've never seen other people complaining, so this should be fine.
> There are some discussions around `chrome.storage`. Is it slow? I noticed it was a bit slow when creating a new tab but it seems to have gone away.
It is up to 10 times slower than localStorage or indexedDB, but only with very large and very deeply nested objects.
> Even then, is `IndexedDB` still the best choice?
IndexedDB can directly store binary types like Blob, File, ArrayBuffer, typed arrays, Map, Set. It also has a much richer API, and it's faster than chrome.storage, although you won't notice it on a small simple value. Using this API is awkward, but there are libraries, and it's not that hard to write your own simple promisifier for the types of operations you need, e.g. based on JS Proxy.
The fastest storage though is the oldest one, window.localStorage. Since this storage has been optimized for decades, its speed is exceptional. It can only store strings, so you'll need JSON.stringify, and it can't be used in a service worker. There's a sentiment saying that synchronous access is bad, but programming shouldn't be based on sentiments: always do a proper test for your use case and choose the actually better solution.