mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-05 17:05:53 +08:00
fix(routing): redirect before rendering, not while rendering
/start and /login are pure forwarders, and both did their `await navigateTo()` in `<script setup>` with no template at all. That await suspends the first render and the navigation aborts it, so the address bar flips to the target while nothing is ever painted — a dark, empty page until the visitor reloads. That is what people hit coming from the website: logging in there sends an already-signed-in user to /start, which redirects here, and here they landed on nothing. Reloading worked, which is exactly the tell — a cold load of the target has no suspended render to abort. Both redirects moved into route middleware, which runs before the page renders, and both pages keep a placeholder template so a component without one can never be what is on screen. /start still passes its query string through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,21 +8,33 @@ import { buildIssuerLoginUrl } from '~~/shared/utils/ssoHandoff'
|
||||
* monorepo that path resolved to the website's login form. Since the split it
|
||||
* resolved to nothing, so signing out dropped the user on a 404.
|
||||
*/
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
// Forwarding from middleware, not from setup: an `await navigateTo()` in
|
||||
// setup aborts the render it suspends, which leaves a cold load sitting on a
|
||||
// blank page instead of moving it along.
|
||||
middleware: [
|
||||
(to) => {
|
||||
const config = useRuntimeConfig()
|
||||
const issuer = String(config.public.authIssuer || '').replace(/\/+$/, '')
|
||||
const raw = String(to.query.redirect || '/')
|
||||
// Only same-origin paths — never bounce onward to an absolute URL from the query.
|
||||
const target = raw.startsWith('/') && !raw.startsWith('//') ? raw : '/'
|
||||
|
||||
definePageMeta({ layout: false })
|
||||
if (issuer) {
|
||||
return navigateTo(buildIssuerLoginUrl(issuer, window.location.origin, target), {
|
||||
external: true,
|
||||
})
|
||||
}
|
||||
|
||||
const issuer = String(config.public.authIssuer || '').replace(/\/+$/, '')
|
||||
const raw = String(route.query.redirect || '/')
|
||||
// Only same-origin paths — never bounce onward to an absolute URL from the query.
|
||||
const target = raw.startsWith('/') && !raw.startsWith('//') ? raw : '/'
|
||||
|
||||
if (issuer) {
|
||||
await navigateTo(buildIssuerLoginUrl(issuer, window.location.origin, target), { external: true })
|
||||
}
|
||||
else {
|
||||
// AUTH_MODE=open: no login exists at all, every request is the local identity.
|
||||
await navigateTo(target, { replace: true })
|
||||
}
|
||||
// AUTH_MODE=open: no login exists at all, every request is the local identity.
|
||||
return navigateTo(target, { replace: true })
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Never seen: the middleware above forwards before this page renders. -->
|
||||
<div />
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
definePageMeta({ layout: false })
|
||||
|
||||
// This page was /start in the monorepo and became the app's front page in the
|
||||
// split (see index.vue). Bookmarks, the website's redirect and every link that
|
||||
// predates the split still point at /start, so catch it here instead of
|
||||
// showing a 404 — the app should not depend on the website being redeployed
|
||||
// for its own old URLs to work.
|
||||
await navigateTo({ path: '/', query: route.query }, { replace: true })
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
// The redirect has to happen in middleware, not in setup. `await navigateTo()`
|
||||
// inside setup suspends the very first render, and the navigation it waits on
|
||||
// aborts that render: the URL flips to `/` but nothing is ever painted, so the
|
||||
// visitor sits on a blank page until they reload. Middleware runs before the
|
||||
// page renders at all, which is the only place a pure redirect route belongs.
|
||||
middleware: [to => navigateTo({ path: '/', query: to.query }, { replace: true })],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Never seen: the middleware above redirects before this page renders. It
|
||||
exists so a component without a template can never be what is on screen. -->
|
||||
<div />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user