kit: Redirects in `load` functions break when running client-side

Describe the bug

There appears to be no way to use the new redirect method and disable SSR globally.

Reproduction

  1. Create a new skeleton project
  2. Create src/routes/page.ts with the contents:
import { redirect } from "@sveltejs/kit";

export function load() {
  throw redirect(307, "/about")
}
  1. Create src/hooks.ts with the contents:
import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
  return resolve(event, { ssr: false })
}
  1. Navigating to root now results in an error page, with the redirect class passed to it, instead of being redirected to “/about”

Repo: https://github.com/WaltzingPenguin/sveltekit-redirect-hooks

Logs

No response

System Info

System:
    OS: Windows 10 10.0.19043
    CPU: (12) x64 AMD Ryzen 5 1600 Six-Core Processor
    Memory: 7.50 GB / 15.95 GB
  Binaries:
    Node: 16.15.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 7.20.3 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 104.0.5112.81
    Edge: Spartan (44.19041.1266.0), Chromium (104.0.1293.54)
    Internet Explorer: 11.0.19041.1566
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.64
    @sveltejs/kit: next => 1.0.0-next.413
    svelte: ^3.44.0 => 3.49.0
    vite: ^3.0.0 => 3.0.8

Severity

blocking an upgrade

Additional Information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 22
  • Comments: 41 (24 by maintainers)

Commits related to this issue

Most upvoted comments

Progress update — there’s now an PR against Vite that fixes this issue: https://github.com/vitejs/vite/pull/9848

As a simple workaround I’ve done the following:

const location = '/login';
if (browser) return await goto(location);
else throw redirect(302, location);

This worked for me in browser and SSR, but yes ideally redirect() should work client side.

EDIT: may not work with links using sveltekit:prefetch

I’ve opened an issue on the Vite repo:

Would love to try and get it fixed properly rather than bodging in a workaround, as part of the reason for the instanceof checks is that TypeScript throws a hissy fit without them

As a simple workaround I’ve done the following:

const location = '/login';
if (browser) return await goto(location);
else throw redirect(302, location);

This worked for me in browser and SSR, but yes ideally redirect() should work client side.

Using goto as a workaround breaks as soon as a link has sveltekit:prefetch enabled. Hovering over the link will immediately redirect without requiring a click.

I agree, this bug is preventing me from migrating my app because it breaks key functionalities.

Can we please also have the warning in the migration guide https://github.com/sveltejs/kit/discussions/5774 on the “Redirects” section? I was wasting quite a lot of time until I consulted the docs and found this issue.

Unless it’s slated to be resolved very soon it might be worth throwing a warning in the docs, because it’ll catch out anyone that tries to use throw redirect or throw error in load and leave them scratching their heads atm. This is the last major issue stopping me from pushing a fairly large app migrated to the ‘new’ sveltekit live so hoping it gets sorted upstream soon 🤞🏼

I think calling goto in the +page.svelte file should work. Its far from ideal but if it works it can be a temporary workaround.

I’m experiencing the same situation without disabling SSR:

// file: src/routes/+layout.ts

import {
  envelopeGuard,
  envelopeGuardRedirect,
} from '$lib/utils/guards';
import { accounts } from '$lib/stores/accountsStore';
import { get } from 'svelte/store';

export async function load({ url }) {
  const { pathname } = url;
  const accountsArray = get(accounts);
  const isRedirect = envelopeGuard({ pathname, accountsArray });
  const location = '/';
  if (isRedirect) {
    throw envelopeGuardRedirect(location);
  } else {
    return {
      key: pathname,
    };
  }
}
// file: src/lib/utils/guards.ts

import { redirect } from '@sveltejs/kit';
export function envelopeGuard({ pathname, accountsArray }) {
  if (
    accountsArray &&
    accountsArray.length === 0 &&
    pathname === '/create-envelope'
  ) {
    return true;
  }
  return false;
}

export const envelopeGuardRedirect = (location: string) =>
  redirect(302, location);

Then when triggering the guard and navigating to /create-envelope I get:

Uncaught (in promise) Redirect {status: 302, location: '/'}

Is it worth a temporary patch to remove the use of instanceof from SvelteKit until the correct solution can be figured out with Vite? I have encountered a similar issue in the past and just used Symbols to work around it, instead of digging into Vite internals.

const redirect_symbol = Symbol.for('sveltekit:redirect')

export class Redirect {
	[redirect_symbol] = true;
	constructor(status, location) {
		this.status = status;
		this.location = location;
	}
}

export function is_redirect(obj) {
	return (redirect_symbol in obj)
}

We should watch to see if this fixes it: https://github.com/vitejs/vite/pull/9730 and https://github.com/vitejs/vite/pull/9759

update from blu: it doesn’t fix it

this part could be the culprit from looking around:

a hunch i haven’t yet tested is that if we used the same runtime_base logic for client/start.js as we do for other things, everything would get resolved the same way.

The logic is basically this: if @sveltejs/kit is installed inside the project, do this…

import { start } from '/node_modules/@sveltejs/kit/src/runtime/client/start.js';

…otherwise (i.e. it’s in a workspace root), use /@fs:

import { start } from '/@fs/path/to/repo/node_modules/@sveltejs/kit/src/runtime/client/start.js';

As I understand it that would match Vite’s internal resolution logic more closely. As things stand we’re doing some things with /node_modules/... and other things with /@fs, which might be at least part of the problem here.

as @WaltzingPenguin said, using goto as a workaround breaks with sveltekit:prefetch which will instantly trigger the redirect

As a simple workaround I created a function for now:

import { browser } from '$app/env'
import { redirect } from '@sveltejs/kit'
import { goto } from '$app/navigation'

const redirectWorkaround: typeof redirect = (status, location) => {
  if (!browser) throw redirect(status, location)
  else {
    goto(location)
  }
}

EDIT: as @madeleineostoja pointed out: make sure you don’t have sveltekit:prefetch enabled for these routes if you use this workaround.