N
Nuxt2mo ago
Felix

Nitro useStorage: delivering Brotli encoded JSON data fails in local development

I have a Nuxt 4 server route that should deliver a 400kb Brotli encoded JSON object. The json file is delivered over a custom server api route, because I need to set some custom headers for cache management with etags and set the correct content-type header. My API route works perfectly on production (Vercel) but fails in production mode. It seems like the browser decoding fails for some reason (maybe corrupted Brolti data sent to the browser?). I already successfully decompressed my brotli encoded file using brotli -d file.json.br
GET http://localhost:3000/api/character-parts-female net::ERR_CONTENT_DECODING_FAILED 200 (OK)
GET http://localhost:3000/api/character-parts-female net::ERR_CONTENT_DECODING_FAILED 200 (OK)
export default defineEventHandler(async (event) => {
const metadata = await useStorage<{ hash: string }>('assets:server').getItem(
`character-parts-female-metadata.json`,
)
if (!metadata)
return new Response('Failed to get metadata', { status: 500 })

const ifNoneMatch = getRequestHeader(event, 'if-none-match')

if (ifNoneMatch === metadata.hash) {
setResponseStatus(event, 304)
return null
}

const characterParts = await useStorage<string>('assets:server').getItemRaw(
`character-parts-female.json.br`,
)
if (!characterParts)
return new Response('Failed to get character parts', { status: 500 })

setHeaders(event, {
'Content-Type': 'application/json',
'Content-Encoding': 'br',
'ETag': metadata.hash,
'Cache-Control': 'no-cache',
})

return characterParts
})
export default defineEventHandler(async (event) => {
const metadata = await useStorage<{ hash: string }>('assets:server').getItem(
`character-parts-female-metadata.json`,
)
if (!metadata)
return new Response('Failed to get metadata', { status: 500 })

const ifNoneMatch = getRequestHeader(event, 'if-none-match')

if (ifNoneMatch === metadata.hash) {
setResponseStatus(event, 304)
return null
}

const characterParts = await useStorage<string>('assets:server').getItemRaw(
`character-parts-female.json.br`,
)
if (!characterParts)
return new Response('Failed to get character parts', { status: 500 })

setHeaders(event, {
'Content-Type': 'application/json',
'Content-Encoding': 'br',
'ETag': metadata.hash,
'Cache-Control': 'no-cache',
})

return characterParts
})
3 Replies
kapa.ai
kapa.ai2mo ago
Hi @Felix. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the knowledge sources, I can see you're using Nitro's storage layer with useStorage to serve Brotli-encoded content. The issue you're experiencing in local development (while it works in production on Vercel) is likely related to how the development server handles the response. Here are a few potential issues and solutions: 1. Return the Raw Buffer Directly When using getItemRaw(), you're getting the raw binary data. However, returning it directly from the event handler might cause Nitro's development server to process it incorrectly. According to the Server documentation, you should use h3's helper functions for sending specific response types. Try using send() from h3 to send the raw buffer:
import { send } from 'h3'

export default defineEventHandler(async (event) => {
const metadata = await useStorage<{ hash: string }>('assets:server').getItem(
`character-parts-female-metadata.json`,
)
if (!metadata)
return new Response('Failed to get metadata', { status: 500 })

const ifNoneMatch = getRequestHeader(event, 'if-none-match')

if (ifNoneMatch === metadata.hash) {
setResponseStatus(event, 304)
return null
}

const characterParts = await useStorage('assets:server').getItemRaw(
`character-parts-female.json.br`,
)
if (!characterParts)
return new Response('Failed to get character parts', { status: 500 })

setHeaders(event, {
'Content-Type': 'application/json',
'Content-Encoding': 'br',
'ETag': metadata.hash,
'Cache-Control': 'no-cache',
})

return send(event, characterParts)
})
import { send } from 'h3'

export default defineEventHandler(async (event) => {
const metadata = await useStorage<{ hash: string }>('assets:server').getItem(
`character-parts-female-metadata.json`,
)
if (!metadata)
return new Response('Failed to get metadata', { status: 500 })

const ifNoneMatch = getRequestHeader(event, 'if-none-match')

if (ifNoneMatch === metadata.hash) {
setResponseStatus(event, 304)
return null
}

const characterParts = await useStorage('assets:server').getItemRaw(
`character-parts-female.json.br`,
)
if (!characterParts)
return new Response('Failed to get character parts', { status: 500 })

setHeaders(event, {
'Content-Type': 'application/json',
'Content-Encoding': 'br',
'ETag': metadata.hash,
'Cache-Control': 'no-cache',
})

return send(event, characterParts)
})
2. Development vs Production Behavior The difference between development and production might be due to how Nitro handles responses in dev mode versus the production build. The development server might be applying additional transformations or compression that conflicts with your pre-compressed content. 3. Alternative Approach If the issue persists, consider serving the uncompressed JSON in development and only using Brotli compression in production, or let the server handle compression automatically rather than serving pre-compressed files. The Server Storage documentation shows how to use useStorage with various drivers, but doesn't specifically address serving pre-compressed binary content, which might be why you're encountering this edge case in development mode. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@278150133932097536> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Felix
FelixOP2mo ago
Still same issue. I think that useStorage somehow corrupts the brotli encoded file while reading it or sends only half of the data. The brotli encoded file is ~400kb while the source is 25mb

Did you find this page helpful?