How to change routes for single page application?

Hey, I’m trying to configure my routes for single page application.
My index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link href="/css/app.css" rel="stylesheet">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="/js/phoenix.js"></script>
    <script src="/js/app.js"></script>
    <title>MyApp</title>
  </head>
  <body>
    <!-- some elements that are managed by JavaScript and WebSocket -->
  </body>
</html>

What is the simplest way to implement routes without controllers, layouts etc?

1 Like

After lots of tries I finally find what I’m looking for:

defmodule MyApp.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/" do
    send_file(conn, 200, "index.html")
  end
end

Helpful docs: Plug.Router and &Plug.Conn.send_file/1.

1 Like

I would still have an index.html.eex template. It will be precompiled then you can render it as normal in a PageController.index action. This way you don’t have to hit disk on every page just to render the home page. That will be a huge performance hit.

4 Likes

@chrismccord: ah, I see your point
What do you think about:

defmodule MyApp.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  alias Phoenix.Controller

  get "/" do
    conn
    |> Controller.put_layout(false)
    |> Controller.put_view(MyApp.PageView)
    |> Controller.render("index.html")
  end
end

Will it work?
I do not need it as .eex file, because I don’t use Elixir code there …
In normal case I will use Controllers, but here I have just index.html, app.css, app.js and phoenix.js files and all happens with Websocket connection.

1 Like

I would still make the “static” index.html into an index.html.eex template so you get the all the precompiled goodies. What you showed w/ the Plug.Router can work, but I would honestly just keep it part of your regular phoenix router and render a SomeController.index action and template.

3 Likes

@chrismccord: Yup, I know, but here I do not need all of that goodies. :smile:
Of course caching (by &render/2 method as you suggested) index.html.eex file is always important, but I will keep rest: empty view and no layout. tt’s just small and simple HTML5 WebSocket client.
Thank you for your help!

Final solution:

defmodule MyApp.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  alias Phoenix.Controller

  get "/" do
    conn
    |> Controller.put_layout(false)
    |> Controller.put_view(MyApp.PageView)
    |> Controller.render("index.html")
  end
end

Helpful docs: Plug.Router and Phoenix.Controller.

1 Like

Had a similar question. I’m currently doing the following in lib/app/web/router.ex:

  scope "/", App.Web do
    pipe_through :browser # Use the default browser stack

    get "/*anything", PageController, :index
  end

This has the advantage of working with my client-side router, so links like /login still route to the controller but renders the correct client-side view. Unfortunately this blocks access to /socket, which I thought would still work since it gets configured outside the router in the Endpoint.

Is there a way around this? Basically I want to let everything through except for /socket, which should be handled outside the router. Another option might be telling the router to intercept /socket before /*anything and just not handle it, letting UserSocket deal with it.

Thanks.

4 Likes