How to redirect the page from the backend
Redirecting to another page is a common task that can be done from the backend by patching a script tag into the DOM using a datastar-patch-elements SSE event. Since this results in a browser redirect, existing signals will not persist to the new page.
Goal #
Our goal is to indicate to the user that they will be redirected, wait 3 seconds, and then redirect them to /guide, all from the backend.
Steps #
We’ll place a data-on:click attribute on a button and use the get action to send a GET request to the backend. We’ll include an empty indicator div to show the user that they will be redirected.
We’ll set up our backend to first send a datastar-patch-elements event with a populated indicator fragment, then wait 3 seconds, and then send another datastar-patch-elements SSE event to append a script tag that redirects the page.
All SDKs provide an ExecuteScript helper function that wraps the provided code in a script tag and patches it into the DOM.
1(require
2 '[starfederation.datastar.clojure.api :as d*]
3 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]
4 '[some.hiccup.library :refer [html]])
5
6
7(defn handle [ring-request]
8 (->sse-response ring-request
9 {on-open
10 (fn [sse]
11 (d*/patch-elements! sse
12 (html [:div#indicator "Redirecting in 3 seconds..."]))
13 (Thread/sleep 3000)
14 (d*/execute-script! sse "window.location = \"/guide\"")
15 (d*/close-sse! sse)}))1using StarFederation.Datastar.DependencyInjection;
2
3app.MapGet("/redirect", async (IDatastarService datastarService) =>
4{
5 await datastarService.PatchElementsAsync("""<div id="indicator">Redirecting in 3 seconds...</div>""");
6 await Task.Delay(TimeSpan.FromSeconds(3));
7 await datastarService.ExecuteScriptAsync("""window.location = "/guide";""");
8});1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.sanic import datastar_response
3
4@app.get("/redirect")
5@datastar_response
6async def redirect_from_backend():
7 yield SSE.patch_elements('<div id="indicator">Redirecting in 3 seconds...</div>')
8 await asyncio.sleep(3)
9 yield SSE.execute_script('window.location = "/guide"')1use datastar::prelude::*;
2use async_stream::stream;
3use core::time::Duration;
4
5Sse(stream! {
6 yield PatchElements::new("<div id='indicator'>Redirecting in 3 seconds...</div>").into();
7 tokio::time::sleep(core::time::Duration::from_secs(3)).await;
8 yield ExecuteScript::new("window.location = '/guide'").into();
9}); 1import { createServer } from "node:http";
2import { ServerSentEventGenerator } from "../npm/esm/node/serverSentEventGenerator.js";
3
4const server = createServer(async (req, res) => {
5
6 ServerSentEventGenerator.stream(req, res, async (sse) => {
7 sse.patchElements(`
8 <div id="indicator">Redirecting in 3 seconds...</div>
9 `);
10
11 setTimeout(() => {
12 sse.executeScript(`window.location = "/guide"`);
13 }, 3000);
14 });
15});Note that in Firefox, if a redirect happens within a script tag then the URL is replaced, rather than pushed, meaning that the previous URL won’t show up in the back history (or back/forward navigation).
To work around this, you can wrap the redirect in a setTimeout function call. See issue #529 for reference.
1(require
2 '[starfederation.datastar.clojure.api :as d*]
3 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]
4 '[some.hiccup.library :refer [html]])
5
6
7(defn handle [ring-request]
8 (->sse-response ring-request
9 {on-open
10 (fn [sse]
11 (d*/patch-elements! sse
12 (html [:div#indicator "Redirecting in 3 seconds..."]))
13 (Thread/sleep 3000)
14 (d*/execute-script! sse
15 "setTimeout(() => window.location = \"/guide\")"
16 (d*/close-sse! sse))}))1using StarFederation.Datastar.DependencyInjection;
2
3app.MapGet("/redirect", async (IDatastarService datastarService) =>
4{
5 await datastarService.PatchElementsAsync("""<div id="indicator">Redirecting in 3 seconds...</div>""");
6 await Task.Delay(TimeSpan.FromSeconds(3));
7 await datastarService.ExecuteScriptAsync("""setTimeout(() => window.location = "/guide");""");
8}); 1val generator = ServerSentEventGenerator(response)
2
3generator.patchElements(
4 elements =
5 """
6 <div id="indicator">Redirecting in 3 seconds...</div>
7 """.trimIndent(),
8)
9
10Thread.sleep(3 * ONE_SECOND)
11
12generator.executeScript(
13 script = "setTimeout(() => window.location = '/guide')",
14)1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.sanic import datastar_response
3
4@app.get("/redirect")
5@datastar_response
6async def redirect_from_backend():
7 yield SSE.patch_elements('<div id="indicator">Redirecting in 3 seconds...</div>')
8 await asyncio.sleep(3)
9 yield SSE.execute_script('setTimeout(() => window.location = "/guide")')1use datastar::prelude::*;
2use async_stream::stream;
3use core::time::Duration;
4
5Sse(stream! {
6 yield PatchElements::new("<div id='indicator'>Redirecting in 3 seconds...</div>").into();
7 tokio::time::sleep(core::time::Duration::from_secs(3)).await;
8 yield ExecuteScript::new("setTimeout(() => window.location = '/guide')").into();
9}); 1import { createServer } from "node:http";
2import { ServerSentEventGenerator } from "../npm/esm/node/serverSentEventGenerator.js";
3
4const server = createServer(async (req, res) => {
5
6 ServerSentEventGenerator.stream(req, res, async (sse) => {
7 sse.patchElements(`
8 <div id="indicator">Redirecting in 3 seconds...</div>
9 `);
10
11 setTimeout(() => {
12 sse.executeScript(`setTimeout(() => window.location = "/guide")`);
13 }, 3000);
14 });
15});Some SDKs provide a helper method that automatically wraps the statement in a setTimeout function call, so you don’t have to worry about doing so (you’re welcome!).
1(require
2 '[starfederation.datastar.clojure.api :as d*]
3 '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]
4 '[some.hiccup.library :refer [html]])
5
6
7(defn handler [ring-request]
8 (->sse-response ring-request
9 {on-open
10 (fn [sse]
11 (d*/patch-elements! sse
12 (html [:div#indicator "Redirecting in 3 seconds..."]))
13 (Thread/sleep 3000)
14 (d*/redirect! sse "/guide")
15 (d*/close-sse! sse))}))1using StarFederation.Datastar.DependencyInjection;
2using StarFederation.Datastar.Scripts;
3
4app.MapGet("/redirect", async (IDatastarService datastarService) =>
5{
6 await datastarService.PatchElementsAsync("""<div id="indicator">Redirecting in 3 seconds...</div>""");
7 await Task.Delay(TimeSpan.FromSeconds(3));
8 await datastarService.Redirect("/guide");
9});1from datastar_py import ServerSentEventGenerator as SSE
2from datastar_py.sanic import datastar_response
3
4@app.get("/redirect")
5@datastar_response
6async def redirect_from_backend():
7 yield SSE.patch_elements('<div id="indicator">Redirecting in 3 seconds...</div>')
8 await asyncio.sleep(3)
9 yield SSE.redirect("/guide")Conclusion #
Redirecting to another page can be done from the backend thanks to the ability to patch script tags into the DOM using the datastar-patch-elements SSE event, or to execute JavaScript using an SDK.