Skip to content

Better support for form action redirects inside handle() hook #9555

@abirtley

Description

@abirtley

Describe the problem

I would like to use hooks to handle things like ensuring session validity. This way, I can be sure that security settings are applied sitewide, and not have to remember to apply them in every single form action handler.

But hooks don't play nicely with redirects when it's a form action handler.

Let's say you have this handler:

// hooks.server.ts
export const handle = async () => {
  if (!isLoggedIn()) {
    throw redirect(303, '/login');
  }
};

and these actions:

// +page.server.ts
export const actions = {
  alwaysRedirects: async () => {
    throw redirect(303, '/');
  },
  sometimesRedirects: async () => ({data: 'foo'}),
};

and a .svelte file that has <Form use:enhance=...> and calls applyAction(result) if result.type === 'redirect'

It plays out as follows:

  • If your form action is /?alwaysRedirects, you always get redirected ✅
  • If your form action is /sometimesRedirects, and you are logged in, you get {data: 'foo'}
  • If your form action is /sometimesRedirects, and you are logged out, then:
    • If javascript is disabled, you get redirected as you would expect ✅
    • if javascript is enabled, applyAction(result) doesn't work because result is {"type":"error","error":Error('SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON')'}

I believe this is because throw redirect() is handled differently when you're in a form action handler vs when you're in a generic endpoint. So, when you throw a redirect from inside the handle() hook, SvelteKit doesn't "know" that it's a form action, and so doesn't return the redirect information in a format that the client javascript understands. Instead, the client tries to parse the / route (which has been returned as HTML) as JSON, and fails.

Describe the proposed solution

I would like to see:

  • Documentation telling me how I can achieve this using existing functionality (I have looked...)

Failing that, I would like to see:

  • If you throw a redirect inside the handle() hook, and you happen to be handling a form action at the time, the response is returned to the client in a manner that lets them redirect using applyAction(), as if the redirect were thrown inside the action handler itself.
    • This could potentially be done using my header inspection and JSON object returning workaround, discussed below. If this is a "good first issue", I'd be happy to take this on with a small pointer in the right direction.

I guess you could also have a separate hook for form actions, inside which throw redirect(...) would work as expected.

Alternatives considered

My current workaround is to inspect the request headers and, if an enhanced form is detected, return a Svelte redirect object instead of an actual redirect.

// inside handle() in hooks.server.ts

if (!isLoggedIn()) {
  const javascriptFormSubmission = event.request.headers.get('x-sveltekit-action') === 'true';
  if (javascriptFormSubmission) {
    return new Response(
      JSON.stringify({
        type: 'redirect',
        status: 303,
        location: '/login',
      } satisfies ActionResult)
    );
  } else {
    throw redirect(303, `/login`);
  }
}

Importance

nice to have

Additional Information

This is currently a nice-to-have, given the workaround is pretty simple, but I thought I'd write it up here in case other people encounter the same issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions