sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.77k stars 1.96k forks source link

make `adapter-node` base path configurable via environment variable #7242

Open benmccann opened 2 years ago

benmccann commented 2 years ago

When generating a static site we ultimately will be placing it either in the correct folder OR preferably behind a load-balancing reverse proxy that strips off the prefix and have it served from the root. So the prefix should not exist in the build folder at all but the code will need to know to add the prefix when referring to assets.

When generating a dynamic site it can be passed environment variables (or perhaps read a config file) where it gets its base prefix. In the same way the site could exist behind a reverse proxy that strips off the prefix OR it'll handle the prefix itself. It matters not as this is an implementation detail of the adapter. The code needs to know to add the prefix when referring to assets and presumably when doing SSR fetching it'll need to know to add/remove the prefix as necessary.

adapter-node uses Polka to serve its assets, executed with the following code:

const server = polka().use(
    // https://github.com/lukeed/polka/issues/173
    // @ts-ignore - nothing we can do about so just ignore it
    compression$1({ threshold: 0 }),
    handler
);

handler is provided as:

const handler = sequence(
    [
        serve(path.join(__dirname, '/client'), 31536000, true),
        serve(path.join(__dirname, '/static'), 0),
        serve(path.join(__dirname, '/prerendered'), 0),
        ssr
    ].filter(Boolean)
);

The issue is that ssr will strip off base from the beginning of the URL but the three serve calls do not. The ssr function IMHO should not be doing this and instead everything should be mounted at the polka().use() call, something like this:

const server = polka().use(
        BASE_PATH,
    // https://github.com/lukeed/polka/issues/173
    // @ts-ignore - nothing we can do about so just ignore it
    compression$1({ threshold: 0 }),
    handler
);

where BASE_PATH would be set either by reading the config file or come in from an environment variable.

This way if I have a reverse proxy that strips prefixes I can have BASE_PATH set to '' but pass the actual base through a header like X-Forwarded-Prefix for when building URL's.

If I don't strip the prefix, then I can set BASE_PATH to the prefix so its handled inside Polka instead but otherwise the rest of the code remains the same.

Anyway, that's my 2c.

Originally posted by @bundabrg in https://github.com/sveltejs/kit/issues/3726#issuecomment-1049460340

This overlaps a bit with https://github.com/sveltejs/kit/issues/595

dummdidumm commented 2 years ago

Adding another comment from #3726 which is relevant:

Oh, I really need this to be fixed. I cannot use the workaround mentioned earlier because my case is one level more complex as I need to use the generated middleware handler in another express server (directus). I cannot control exactly when to .use(handler) and they bind many other middlewares which means that I have to bind the sveltekit handler only to a single subpath (say /dashboard).

.use(handler) without setting paths.base

Both frontends (sveltekit and directus) are confused and nothing works...

.use(handler) with setting paths.base to /dashboard

Sveltekit frontend displays html pages but scripts are not found. But directus does not load correctly.

.use('/dashboard', handler) without setting paths.base

I can access the sveltekit frontend via /dashboard but internal links (href="{base}/about") are not working as base is empty and javascript cannot be loaded (urls do not contain /dashboard and thus are not handled by the middleware). Directus works without probems.

.use('/dashboard', handler) with setting paths.base to /dashboard

I can access kit via /dashboard/dashboard (yes duplicated) but the the about page is not available at /dashboard/about (which is build from href="{base}/about") but at /dashboard/dashboard/about. Basically I need to duplicate the base path. Strangely now the javascript files can be loaded correctly (at /dashboard/_app/immutable/start-e25cee58.js).

I found I can fix this by removing these lines https://github.com/sveltejs/kit/blob/2a9c19252912d61bd840441fe80dc13366af41f7/packages/kit/src/runtime/server/index.js#L58-L63 Now everything works as expected (i.e. /dashboard and /dashboard/about) at least with my minimal testing.

This is surely not the final solution as the dev server needs those lines and I guess the other adapters need them as well...


I got one step further by adding the base url to the request before passing it to the handler:

    app.use('/dashboard', (req, res, next) => {
      let requestProxy = new Proxy(req, {
        get(target, property) {
          if (property === 'url') {
            return target.baseUrl + (target.url === '/' ? '' : target.url);
          }

          return target[property];
        },
      });

      handler(requestProxy, res, next);
    });

Now the ssr function works correctly (because it strips the basepath we just added) but the static files are not correctly served (because sirv does not strip the basepath) as @bundabrg also noted in https://github.com/sveltejs/kit/issues/3726#issuecomment-1049460340

Originally posted by @fehnomenal in https://github.com/sveltejs/kit/issues/3726#issuecomment-1186534972

gregroyal commented 1 year ago

Anyone have any workarounds to this issue? Its blocking me for dynamic path deployments if i dont know the base at build time

sidharthramesh commented 1 year ago

Same here

Mr-Thack commented 6 months ago

I found this to work:

import CONFIG from "../svelte.config.js"
import { dev } from "$app/environment";
...
redirect(307, (dev? "" : CONFIG.paths.base) + "/normal/redirect");

Oh wait a second. I think I fixed it. If I reply below, then know that I've "fixed" it. If not, then try using this in your code.

Mr-Thack commented 6 months ago

nvm, it "works" but it causes like 100 recursive definition errors which (depending on the environment) could cause the build/ folder to be missing some very very important js files.

Mr-Thack commented 6 months ago

Ok, so I found another solution. This solution will only work if you will be setting the base path at compile time. If you are doing it right before launching the executable, I think you could use dynamic env variables instead and manually route everything. Or perhaps intercept the default router.

So, for this method, you will need 1) base.js - some file in your project root directory. Maybe you could also put it in $lib.

const BASE = "/new";
export default BASE;

2) Modify svelte.config.js:

import BASE from "../base.js";
...
export config = {
  ...
  paths: {
    base: BASE,
    relative: false
  }
 ...
};

3) And finally, modify +hooks.server.ts or whatever +page.server.ts:

// make sure to get the path right
import BASE from "../base.js";
...
redirect(307, BASE + "/login");

This works. But this is a work around, not a fix.