sam-w
sam-w

Reputation: 7687

Set specific headers for static files in SvelteKit

I need to serve some files from my SvelteKit project with permissive CORS headers.

Some options I've explored:

Placing the files in /static

If I place those files in /static I get restrictive CORS headers.

Is there a way to customize headers for static files? I would guess not, based on this conversation: https://github.com/sveltejs/kit/issues/2060.

Placing the files in /lib

I can place the files elsewhere in the directory structure, then access them using readFile from within a +server.ts endpoint.

However, if I place the files in /lib and build, they do not get copied into the build output at an easily accessible path.

I can figure out the correct path manually, but it's brittle and only works when I'm running the pre-built project (i.e. only if I do npm build; npm preview, and not if I do npm dev).

Placing the files elsewhere and configuring an alias

Using the same custom endpoint approach as above, in theory I could:

{
    ...,
    resolve: {
        alias: {
            $public: '/public'
        }
    }
}
readFile(`$public/{params.filename}`)

However, when building, Vite does not rewrite $public to the correct path in the build output. It would appear that this path rewriting only works in certain situations.

Bypassing the build process

Again using the custom endpoint approach, I could add a new step to my Docker build to copy the files to a path outside of the SvelteKit build output and then read them from disk at that path.

Unfortunately I then don't get the benefit of any of the packaging that SvelteKit/Vite does when building my assets (e.g. compression).

Upvotes: 3

Views: 1378

Answers (1)

sam-w
sam-w

Reputation: 7687

For now I have a quick fix in place, which is to:

  • place the files in /static/__public/, where they will be served by SvelteKit at /__public/<filename>.js without the correct CORS headers
  • have my .../public/[filename]/+server.ts make a fetch to /__public/<filename>.js, manipulate the headers in the response and send it on its way, i.e.:
// In /src/routes/public/[filename]/+server.ts

export const GET = (async ({ fetch, url, params }) => {
    const response = await fetch(new URL(`/__public/${params.filename}`, url.origin));
    const mimeType = "replace-me"; // I'm using the mime-types library here, i.e. `mime.lookup(params.filename);`
    const headers = {
        ...response.headers,
        'Access-Control-Allow-Origin': `*`,
        'Content-Type': `${mimeType};charset=UTF-8`
    };
    return new Response(response.body, {
        headers
    });
}) satisfies RequestHandler;

export const OPTIONS = (async ({ fetch, url, params }) => {
    const response = await fetch(new URL(`/__public/${params.filename}`, url.origin), {
        method: 'OPTIONS'
    });
    const headers = {
        ...response.headers,
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': '*'
    };
    return new Response(null, {
        headers
    });
}) satisfies RequestHandler;

While it does work, this is suboptimal:

  • I have to make an extra request
  • The files are being served on two paths: /__public/<filename>.js without permissive CORS headers, and /public/<filename>.js with the correct headers.

Upvotes: 1

Related Questions