woylie

woylie

Remove or disable warning about navigating across live sessions

We are seeing a lot of warning logs like this:

navigate event to "https://someurl" failed because you are redirecting across live_sessions. A full page reload will be performed instead

There are cases where it is difficult to determine whether to use navigate or href.

  • A navigation bar that contains links to views in multiple live sessions (e.g. because of different authorization- or CSP-related security contexts).
  • A LiveView that receives a return_to URL as a parameter, which may lead to a different live action in the same view, or a view in the same live session, or a view in a different live session.
  • A LiveComponent that links to a particular view that may or may not be in the same live session, depending on the view in which the component is rendered.

In either case, using navigate by default in the component will lead to the warning log when navigating to a view in a different live session. Using href by default will cause unnecessary full page reloads and degrade the user experience. Switching between navigate and href dynamically requires the component or view to resolve in which live session the current URL is and which URLs will lead to the same live session.

I can see that this warning might be useful in a development context, but since the switch to a full page reload is done automatically anyway, and dynamically choosing between navigate and href is not always feasible, this log is not very actionable and just increases the log volume.

I’d like to suggest to either remove the log completely, or to add a configuration option to disable it (maybe you’d want it in dev, but not in prod).

Most Liked

woylie

woylie

Thank you. Based on that, I made some helpers to deal with links and pushes/redirects dynamically:

@doc """
Uses `Phoenix.LiveView.push_patch/2`, `Phoenix.LiveView.push_navigate/2`, or
`Phoenix.LiveView.redirect/2`, depending on whether the given URL is in the
same live session or live view.

## Example

    push_dynamic(socket, to: ~p"/some/path")
"""
@spec push_dynamic(Phoenix.LiveView.Socket.t(), keyword) ::
        Phoenix.LiveView.Socket.t()
def push_dynamic(socket, opts) do
  url = Keyword.fetch!(opts, :to)

  case navigation_type(socket, url) do
    :patch -> Phoenix.LiveView.push_patch(socket, opts)
    :navigate -> Phoenix.LiveView.push_navigate(socket, opts)
    :href -> Phoenix.LiveView.redirect(socket, opts)
  end
end

@doc """
Takes the socket and a link URL and returns a list with a single `patch`,
`navigate`, or `href` attribute, depending on whether the given URL is in the
same live session or live view.

## Example

```heex
<.link {resolve_link_attrs(@socket, ~p"/some/path")}>Go</.link>
```
"""
@spec resolve_link_attrs(Phoenix.LiveView.Socket.t() | nil, String.t()) ::
        [patch: String.t()] | [navigate: String.t()] | [href: String.t()]
def resolve_link_attrs(%Phoenix.LiveView.Socket{} = socket, url) do
  [{navigation_type(socket, url), url}]
end

# there is no socket in static views
def resolve_link_attrs(nil, url), do: [href: url]

defp navigation_type(%Phoenix.LiveView.Socket{} = socket, url) do
  current_view = socket.view

  # is nil during disconnected mount
  current_session = Map.get(socket.private, :live_session_name)

  case live_session(socket, url) do
    {:ok, ^current_view, ^current_session} -> :patch
    {:ok, _, ^current_session} -> :navigate
    _ -> :href
  end
end

defp live_session(
       %Phoenix.LiveView.Socket{host_uri: %URI{} = host_uri} = socket,
       url
     ) do
  case Phoenix.Router.route_info(
         socket.router,
         "GET",
         url,
         to_string(host_uri)
       ) do
    %{plug: Phoenix.LiveView.Plug, phoenix_live_view: lv} ->
      {view, _action, _opts, live_session} = lv
      {:ok, view, live_session.name}

    %{} ->
      {:error, :not_a_live_view}

    :error ->
      {:error, :not_found}
  end
end

defp live_session(
       %Phoenix.LiveView.Socket{host_uri: :not_mounted_at_router},
       _
     ) do
  {:error, :not_mounted_at_router}
end

This relies on the specifics of the route_info details, and on socket.private.live_session_name. I’m also not sure whether it’s correct to read `socket.view` here, or whether it would need to be socket.private.root_view.

Which parts of this do you think could be added to LiveView? I’d be happy to open a PR once we agree on the API.

LostKobrakai

LostKobrakai

The fallback happens to not break in the users face. It’s not optimal either though. Live navigation happens on the established websocket connection, sending a navigation request to the server, which is rejected there. Then the result is sent back to the client, which then starts with a fresh http request. So you have an additional roundtrip for the fallback to happen.

The optimal case here would be to make your components aware of the difference between plain links to a new live_session and urls they can live navigate to. It would be great if LV would have tools to figure out that distrinction though, to automate this vs. making it implicit knowledge through the developer.

steffend

steffend

Phoenix Core Team

As @LostKobrakai mentioned, silencing the warning is not a good solution as it requires an extra round-trip for the fallback, though if you really want to do that, you could try Logger.put_module_level(Phoenix.LiveView.Channel, :error).

You can check what live session a route is part of by using Phoenix.Router.route_info/4, but this requires relying on implementation details:

def live_session(path, host) do
  case Phoenix.Router.route_info(MyAppWeb.Router, "GET", path, host) do
    %{plug: Phoenix.LiveView.Plug, phoenix_live_view: lv} ->
      {view, _action, _opts, live_session} = lv
      {:ok, view, live_session.name}

    %{} = not_a_live_view ->
      {:error, :not_a_live_view}

    :error ->
      {:error, :not_found}
  end
end

I don’t see a reason why we’d be against adding some official API to do that though :slight_smile:

Where Next?

Popular in Proposals: Ideas Top

sbennett33
When building a component library, it is often useful to give users the ability to customize the underlying element or component to use. ...
New
woylie
We are seeing a lot of warning logs like this: navigate event to "https://someurl" failed because you are redirecting across live_sessio...
New
azyzz228
The slow network is known to be an Achilles heel of LiveView’s architecture. Recently, I was working on creating a fast rendering map wi...
New
andreamancuso
Hey folks, This might sound niche, but I think it’s worth bringing up - especially given Phoenix’s reputation for being lightweight, por...
New
marcandre
I notice that most events have bindings (e.g. phx-keyup) but not the input event. The input event is the preferred way to interact with ...
New
engineeringdept
In 2026 double submit/session tokens are no longer necessary to prevent against CSRF attacks. Instead, we can use the Sec-Fetch-Site head...
New
sevensidedmarble
Hello all, Apologies if this has been proposed before I guess, but I have a very simple one: With the increasing importance of LV, I th...
New
BartOtten
I’d like to propose that we refrain from using the term "DeadView" as the opposite of “LiveView” and instead choose an alternative. A new...
New
spicychickensauce
I’ve previously explored what is possible today with hacks to implement view transitions in our apps: I have since created a fork to im...
New
Flo0807
Hello everyone! Phoenix LiveView v0.18 introduced the special attributes :let, :for and :if. In addition to the :if special attribute, I...
New

Other popular topics Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement