Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Doesn't work for single page apps with client-side routing #22

Open
Ricki-BumbleDev opened this issue Nov 25, 2023 · 8 comments
Open

Doesn't work for single page apps with client-side routing #22

Ricki-BumbleDev opened this issue Nov 25, 2023 · 8 comments

Comments

@Ricki-BumbleDev
Copy link

Ricki-BumbleDev commented Nov 25, 2023

For single page apps with client-side routing the index.html file needs to be served for any path that cannot be resolved otherwise, so that it can be handled by the client-side JS.

From the way it works in Express or Fastify I would expect this to work:

app.use(staticPlugin({ assets: './static', prefix: '/'}));
app.get('/*', () => Bun.file('./static/index.html'));

But with the Elysia static plugin it doesn't work. In this case I get the index.html returned also for my static assets.

Would be nice if this could be addressed.

For now I cannot use the plugin and instead wrote this:

app.get('/*', async ({ path }) => {
  const staticFile = Bun.file(`./static/${path}`);
  const fallBackFile = Bun.file('./static/index.html');
  return (await staticFile.exists()) ? staticFile : fallBackFile;
});

(I'm a little bit worried it might be vulnerable to path-traversal attacks, although from what I have tried it seems fine.)

@aryzing
Copy link

aryzing commented Dec 2, 2023

It works as intended when setting alwaysStatic: true,

const app = new Elysia()
  .use(staticPlugin({ assets: "./static", prefix: "/", alwaysStatic: true }))
  .get("/*", () => Bun.file("./static/index.html"));

The plugin resolves files differently depending on alwaysStatic. When set, it iterates through all the assets and loads their paths as routes into the router rather than trying to dynamically resolve the file at "request time".

It's the "request time" file resolution that is interfering with the app.get("/*", ...) route. Setting alwaysStatic: true avoids the "request time" resolution.

@Ricki-BumbleDev
Copy link
Author

@aryzing Thank you for your response. I tried alwaysStatic: true already. It did not resolve the issue. Do you have a full example where it is working for you?

@aryzing
Copy link

aryzing commented Dec 2, 2023

What version are you on? It works with,

"elysia": "0.7.29",
"@elysiajs/static": "0.7.1",

@bogeychan
Copy link
Collaborator

bogeychan commented Dec 2, 2023

Hi all 👋

you can do this instead of .get("/*"):

import { Elysia } from 'elysia';
import { staticPlugin } from '@elysiajs/static';

const app = new Elysia()
  .onError((ctx) => {
    if (ctx.code === 'NOT_FOUND') {
      ctx.set.redirect = '/';
      return '';
    }
  })
  .use(staticPlugin({ assets: 'app/dist', prefix: '/' }))
  .get('/', () => Bun.file('app/dist/index.html'))
  .listen(8080);

console.log(app.server?.url.toString());

and make sure to exclude folders as described here: #17 (comment)

@Ricki-BumbleDev
Copy link
Author

Ricki-BumbleDev commented Dec 3, 2023

@aryzing I am using the same versions, it's definitely not working like that. It responds with the index.html for everything, even if there is a matching static file.

@aryzing
Copy link

aryzing commented Dec 3, 2023

@Ricki-BumbleDev It may be worth locally adding a console.log() directly inside the if/else to help understand which branch is being called. If it's the first branch and the issue persists, there may be other factors affecting the response.

@KernelKrusha
Copy link

KernelKrusha commented Dec 19, 2023

I can confirm that the suggestion by @bogeychan is not working for SPA's with client side routing since it redirects everything to /, when you would actually want it to route everything to the index.html file while keeping the path in the url.

The workaround suggested by @Ricki-BumbleDev (#22 (comment)) works perfectly.

ikxin added a commit to ikxin/kms-tools that referenced this issue Apr 30, 2024
@myukselen
Copy link

@bogeychan's error handler opened opportunities for me.
I have static files for React SPA under '/app/' base url.

I also want to preserve the path for client side routing to pick where it left off. This is the solution I am using right now:

        .onError((ctx) => {
            if (ctx.code === 'NOT_FOUND') {
                if (ctx.path.startsWith('/app/')) {
                    // ctx.set.redirect = '/app/';  // looses path for the SPA
                    ctx.set.status = 'OK'
                    return Bun.file('../frontend/dist/index.html');
                } else {
                    ctx.set.redirect = '/';
                }
                return '';
            }
        })
        .use(staticPlugin({
            assets: '../frontend/dist',
            prefix: '/app',
        }))

Thanks for this repository and the discussion.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants