#Navigation login

1 messages · Page 1 of 1 (latest)

analog mural
#

Is the logic of this code correct and consistent with astro?

<script>
import { navigate } from "astro:transitions/client";

document.addEventListener("astro:page-load", () => {
    // If the user tries to access from a page other than a form
    if (!history.state?.fromFrom) {
        navigate("/", { history: "replace" });
    }

    // Handle pop state (redirect to home)
    const onPopState = () => {
        navigate("/", { history: "replace" });
    }

    // Add popstate event listener
    window.addEventListener("popstate", onPopState);

    // Popstate event listener cleanup
    document.addEventListener("astro:before-swap", () => {
        window.removeEventListener("popstate", onPopState);
    })
});

</script>

stiff hemlockBOT
#

Hi @analog mural. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Your code uses supported APIs and events, but there are a couple of important consistency points with how Astro’s router and history work.

What matches Astro:

  • Using navigate() from astro:transitions/client is correct for client-side redirects. [navigate]
  • Using history: "replace" is valid and will overwrite the current history entry instead of pushing a new one. [history option]
  • Listening to astro:page-load to run logic on every navigation is exactly what Astro recommends for code that would otherwise run on DOMContentLoaded. [astro:page-load]
  • Cleaning up listeners on astro:before-swap is consistent with the documented lifecycle: astro:before-swap fires before the DOM is replaced, so it’s a good time to remove event listeners tied to the current page. [Lifecycle events]

Things that are not covered or guaranteed by the docs:

#
  • The docs describe navigate()’s state option (data stored in the NavigationHistoryEntry), but they do not document any built-in history.state.fromFrom convention or key. [state option]
    So the check if (!history.state?.fromFrom) is only “correct” if you are setting that state yourself elsewhere via navigate(href, { state: { fromFrom: true } }). Astro does not set this automatically.
  • The docs do not explicitly describe how history.state behaves across all combinations of native navigation and navigate(), so relying on history.state alone for security-sensitive login gating is not documented as a recommended pattern.

About the popstate handling:

  • Using window.addEventListener("popstate", ...) is compatible with Astro’s router: the router itself uses the History API, and the docs explicitly mention combining navigate() with history.back(), history.forward(), etc., so listening to popstate is reasonable. [Trigger navigation]
  • Removing the popstate listener on astro:before-swap is consistent with the documented lifecycle and script re-execution guidance (clean up per-navigation listeners). [Client-side navigation process; Script re-execution]

Summary:

#
  • The event wiring (astro:page-load, astro:before-swap, popstate) and use of navigate("/", { history: "replace" }) are consistent with Astro’s documented APIs and lifecycle.
  • The logic that checks history.state?.fromFrom is only valid if you control and set that state yourself; Astro does not provide or document a fromFrom flag, so that part is not guaranteed by the framework.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@analog mural Kapa.ai is still learning and improving, please let me know how I did by reacting below.

analog mural
#

@stiff hemlock But because of the astro:page-load now my code is excecuted on every page, creating a "/" redirect loop

stiff hemlockBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You’re right that astro:page-load runs on every navigation, so your current logic will keep re-running and can easily cause a loop.

From the docs:

  • astro:page-load fires on first load and after every navigation. [astro:page-load]
  • navigate() with history: "replace" updates the current entry but still completes a navigation, which will fire astro:page-load again. [navigate; history option]

So if history.state?.fromFrom is falsy after you navigate to /, the condition stays true and you keep redirecting.

To avoid the loop, you need some condition that becomes false after the first redirect. The docs show that you can store arbitrary data in the history entry via the state option of navigate(). [state option]

For example (pattern only, adapted from the docs):

import { navigate } from "astro:transitions/client";

document.addEventListener("astro:page-load", () => {
  // Only run this logic on the protected page, not on "/"
  if (location.pathname !== "/protected") return;

  // If we didn't come here with the expected state, redirect once
  if (!history.state?.fromForm) {
    navigate("/", {
      history: "replace",
      state: { fromForm: true }, // mark this entry so it won't redirect again
    });
    return;
  }

  // ...rest of your logic
});

Key points:

#
  • Restrict the logic to the specific route(s) where you need it (e.g. if (location.pathname === "/login")), not every page.
  • When you call navigate(), set a state flag so that on the next astro:page-load the condition is no longer true.
  • Your popstate listener + cleanup on astro:before-swap is fine and consistent with the lifecycle. [Lifecycle events; Script re-execution]

The knowledge sources don’t provide a complete, ready-made pattern for login flows, but they do document astro:page-load, navigate(), and state, which you can combine as above to prevent redirect loops.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: