How to configure layout for landing page (home page)

I’m trying to configure a new layout for the home page (/) so the only way I have found so far is to configure a new pipeline replacing the :root default layout

pipeline :browser_public_layout do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :public}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :fetch_current_user
  end
...
...

scope "/", MyAppWeb do
    pipe_through :browser_public_layout

    get "/", PageController, :home

  end

is this the right way? it’s confusing for me because Page controller has the following code, having layout = false I thought layout equal to false means no layout at all, but still have the default one → :root

def home(conn, _params) do
    # The home page is often custom made,
    # so skip the default app layout.
    render(conn, :home, layout: false)
  end

Also the only way I found was

My first unsuccessful approach was specify the layout in the function but I haven´t found yet the reason why it didn´t work

def home(conn, _params) do
    # The home page is often custom made,
    # so skip the default app layout.
    render(conn, :home, layout: {MyAppWeb.Layouts, :public})
  end

Also trying another approach I thought just remove the layout and again the only way I found was create another pipeline with no default layout just like follow:

pipeline :browser_no_layout do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :fetch_current_user
  end
...
...

scope "/", MyAppWeb do
    pipe_through :browser_no_layout

    get "/", PageController, :home

  end

So my questions to try to understand it are

  • Is the right way to create another pipeline if we just want to use another layout?
  • Is the right way to do it, create a new pipeline if we don´t want any layout at all?
  • Does the function calling render(conn, :home, layout: false) have a way to pointing out to another layout instead of the default one or at least can be used to remove the default layout

The workaround used for me creating new pipelines was inspired by this question Phoenix Multiple Layouts

Thanks in advance

If you’re using LiveView you can group routes in a live_session and pass the root and the layouts there, but since you’re using controllers you need to know two things.

  1. Controllers are also plugs and can add plugs to themselves as if in a pipeline.
  2. Router pipelines are evaluated top to bottom.

From 1 you can pass layouts straight to the controllers using a plug:

defmodule ExampleWeb.PageController do
  use ExampleWeb, :controller

  plug :put_layout, html: {ExampleWeb.Layouts, :admin}

  def home(conn, _params) do
    render(conn, :home)
  end
end

From 2 you can pass another pipeline overriding only the plugs that you want changed:

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {ExampleWeb.Layouts, :root}
    plug :put_layout, html: {ExampleWeb.Layouts, :app}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end


  pipeline :admin_layout do
    plug :put_layout, html: {ExampleWeb.Layouts, :admin}
  end

  scope "/", ExampleWeb do
    pipe_through :browser
    get "/", PageController, :home
  end

  scope "/admin", ExampleWeb do
    pipe_through [:browser, :admin_layout]
    get "/", PageController, :home
  end
2 Likes

Hi @thomas.fortes you have mentioned a couple of things quite interesting.

The first one the used of live_session, good point I haven’t thought on it but I will have it in mind for the future, mainly because right now I was just tinkering with the framework and trying to build a landing home page (with a custom layout or without the default one).

The second one I didn’t know that another option where we can create a new pipeline but only overwriting that property we want to, in this case the layout, it looks more clean and a lot less verbose.

Thanks for your answer

Hello again @thomas.fortes

Reviewing once again the code there is something I have doubts

you wrote this

pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {ExampleWeb.Layouts, :root}
    plug :put_layout, html: {ExampleWeb.Layouts, :app}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

plug :put_layout, html: {ExampleWeb.Layouts, :app}

Is this plug used for setting the layout for the liveviews?
I thought for what I red here Can't use put_layout with liveviews on router? - #10 by josevalim the liveview layout could only be change in every liveview

Hi @joseratts

I made a little demo to show one way to use no layout or different layout on a controller page and also on a Live view
https://github.com/karim-semmoud/multi_layout

The magic actually happens inside your MyAppWeb.ex file, not the router
You can create as many functions for each layout and use accordingly as follow

#lib/multi_layout_web.ex

  def controller_admin do
    quote do
      use Phoenix.Controller,
        formats: [:html, :json],
        layouts: [html: {MultiLayoutWeb.Layouts, :admin}]

      import Plug.Conn
      import MultiLayoutWeb.Gettext

      unquote(verified_routes())
    end
  end

# lib/multi_layout_web/controllers/admin_controller.ex
  use MultiLayoutWeb, :controller_admin

Documentation

This module holds different layouts used by your application.

See the layouts directory for all templates available. The “root” layout is a skeleton rendered as part of the application router. The “app” layout is set as the default layout on both use MultiLayoutWeb, :controller and use MultiLayoutWeb, :live_view.

Note:
By default, the controller uses :app layout, to edit it to a layout other than :app, your can define it this way

def controller do
    quote do
      use Phoenix.Controller,
        formats: [:html, :json],
        layouts: [html: {MultiLayoutWeb.Layouts, :admin}]

      import Plug.Conn
      import MultiLayoutWeb.Gettext

      unquote(verified_routes())
    end
  end

Hope this helps

While those use calls are the place you define default layouts I wouldn‘t suggest using that to change the layout for a set of pages and even less for a single page being different.

For controllers you can use Phoenix.Controller.put_layout/2 (and put_root_layout/2) to customize those layouts for a given request. Those are function plugs, so you can plug them into any place you can use plugs in to target one or more pages.

In a LiveView you can cannot change the root layout anymore (use put_root_layout/2 as described above), but you can customize the layout by returning {:ok, state, layout: …} from mount/3. You can use attach_hook combined with on_mount/live_session to share this across multiple liveviews.

@LostKobrakai

Thank you a lot for the clarification. I updated the example following your recommendations if it can help anyone
https://github.com/karim-semmoud/multi_layout

For a controller:

defmodule MultiLayoutWeb.AdminController do
  use MultiLayoutWeb, :controller

  def admin(conn, _params) do
    conn
    |> Phoenix.Controller.put_layout(html: {MultiLayoutWeb.Layouts, :admin})
    |> render(:admin)
  end
end

For a liveview

defmodule MultiLayoutWeb.AdminLive do
  use MultiLayoutWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket, layout: {MultiLayoutWeb.Layouts, :admin}}
  end
end

Thank you both @karim-semmoud @LostKobrakai

I’m gonna clone and test the code shared by @karim-semmoud play around with it in order to understand quite well the concept and the code.

My main goal is be able to show 2 different layout depending on the user role once it logged in. By now I think set those layout as root layout because it seems easier to maintain to me avoiding add code to several liveviwes I have defined. However it still to early to me asseverate I’m gonna do it in that way (until I have “mastered” the layouts thing).

I lot of thanks to all you I’m learning a lot!!!