Section Components

The section component pattern

Define section renderers

# lib/my_app_web/components/sections.ex
defmodule MyAppWeb.Sections do
  use Phoenix.Component

  # Function name matches the section template name
  def hero(assigns) do
    ~H"""
    <section class="hero">
      <h1>{@fields["heading"]}</h1>
      <p>{@fields["subheading"]}</p>
    </section>
    """
  end

  def richtext(assigns) do
    ~H"""
    <section class="prose">
      {raw(@fields["body"])}
    </section>
    """
  end
end

Access field values

# String fields
@fields["heading"]

# Richtext (HTML string, use raw/1)
raw(@fields["body"])

# Media (resolve slug to URL)
Brix.Render.media_url(@fields["image"])

# List fields
for item <- @fields["items"] do
  item["label"]
end

# Boolean fields
if @fields["visible"] do
  # ...
end

Rendering Sections & Layouts

Using Brix.Render components

Render page sections

<%!-- Dispatches each section to the matching
     function in MyAppWeb.Sections --%>
<Brix.Render.sections
  sections={@page.sections}
  module={MyAppWeb.Sections}
/>

Each section's template field is used to call the corresponding function component in the given module.

Render a layout with content

<%!-- Renders header sections, inner block,
     then footer sections --%>
<Brix.Render.layout
  layout={@layout}
  module={MyAppWeb.Sections}
>
  <%!-- Page content goes here --%>
  <Brix.Render.sections
    sections={@page.sections}
    module={MyAppWeb.Sections}
  />
</Brix.Render.layout>

Resolve media URLs

# In templates
<img src={Brix.Render.media_url("hero-image")} />

# Returns "/content/media/{path}" or ""
Brix.Render.media_url("hero-image")

SEO in Root Layout

Using Brix.Meta.field/3 for meta tags

Root layout meta tags

<%!-- root.html.heex --%>
<head>
  <title>{Brix.Meta.field(@page, @site, :title)}</title>

  <meta
    name="description"
    content={Brix.Meta.field(@page, @site, :description)}
  />
  <meta
    property="og:title"
    content={Brix.Meta.field(@page, @site, :og_title)}
  />
  <meta
    property="og:description"
    content={Brix.Meta.field(@page, @site, :og_description)}
  />
  <meta
    property="og:image"
    content={Brix.Meta.field(@page, @site, :og_image)}
  />
</head>

Available meta fields

AtomFallback chain
:titlepage.meta_title -> page.title -> site.meta_title -> site.name
:descriptionpage.meta_description -> site.meta_description
:og_titlepage.og_title -> :title fallback
:og_descriptionpage.og_description -> :description fallback
:og_imagepage.og_image -> site.og_image

Works with both %Page{} and %Collection{} structs.

Slug Redirects

Handling old URLs

In a LiveView or controller

def handle_params(%{"path" => path}, _uri, socket) do
  slug = "/" <> Enum.join(path, "/")

  case Brix.get_page(slug) do
    {:ok, page} ->
      {:noreply, assign(socket, page: page)}

    :error ->
      case Brix.find_redirect(slug) do
        {:ok, new_slug} ->
          {:noreply,
           push_navigate(socket, to: new_slug)}

        :error ->
          raise MeWeb.NotFoundError
      end
  end
end

Define slug history in page.yml

# pages/blog/new-post/page.yml
title: My Post
slug_history:
  - /blog/old-post
  - /posts/my-post

Old slugs automatically resolve via Brix.find_redirect/1.

Store Setup

Configuring Brix.Store.Filesystem

application.ex

# lib/my_app/application.ex
def start(_type, _args) do
  children = [
    {Brix.Store.Filesystem,
     content_dir:
       Path.join(:code.priv_dir(:my_app), "content")},
    # ... other children
  ]

  opts = [strategy: :one_for_one, name: MyApp.Supervisor]
  Supervisor.start_link(children, opts)
end

Runtime reload

# Reload content after editing files
Brix.reload()

# Validate content before loading
Brix.Validator.validate(content_dir)
#=> %{errors: [], warnings: []}

On boot, content is validated automatically. Errors block loading; warnings are logged.