Xenia.Metadata (Xenia v0.1.1)

Copy Markdown View Source

Page titles, descriptions and social-card tags for an app folder.

A page, and any layout.ex above it, can define metadata/1. It gets the page's assigns and returns a map:

# app/layout.ex: the defaults for every page
def metadata(_assigns) do
  %{
    title: "Acme",
    title_template: "%s · Acme",
    description: "Acme makes rockets.",
    open_graph: %{site_name: "Acme", type: "website"}
  }
end

# app/posts/[slug]/page.ex
def metadata(assigns) do
  %{title: assigns.post.title, description: assigns.post.summary}
end

Xenia merges them from the outermost layout to the page: a key set lower down wins, and open_graph and twitter merge key by key. The page's title goes through the nearest title_template ("Hello · Acme"); a page without a title gets its layouts' title as it is.

The result is assigned as @xenia_metadata, and its title as @page_title, so the browser tab follows live navigation like any page_title. It's computed after the page has its data: after mount/3, or after the page's own handle_params/3 if it has one.

A page that assigns page_title itself, and has no metadata title, keeps its page_title: a layout's default title only fills in for pages without one.

Render the tags in root.html.heex, inside <head>:

<.live_title default="Acme">{assigns[:page_title]}</.live_title>
<Xenia.Metadata.tags metadata={assigns[:xenia_metadata]} />

The root layout is rendered once, on the first page load, which is what search engines and link previews read. On live navigation LiveView updates the <title>, but not the other tags in <head>.

Keys

  • :title - the page title
  • :title_template - wraps titles set below this layout; %s is the title
  • :description - <meta name="description">
  • :canonical - <link rel="canonical">, a full URL
  • :robots - <meta name="robots">, e.g. "noindex"
  • :open_graph - og: tags, e.g. %{type: "article", image: "https://..."}. og:title and og:description default to the title and description
  • :twitter - twitter: tags, e.g. %{card: "summary_large_image"}

Any other key raises an ArgumentError, so a typo doesn't go unnoticed.

Summary

Functions

Merges the metadata of layouts (outermost first) and page, called with assigns. Modules without metadata/1 are skipped. Returns nil when none of them has any.

Renders the <meta> and <link> tags for resolved metadata. Put it in root.html.heex, inside <head>, next to <.live_title>, which renders the title.

Types

t()

@type t() :: %{optional(atom()) => String.t() | map()}

Functions

resolve(layouts, page, assigns)

@spec resolve([module()], module() | nil, map()) :: t() | nil

Merges the metadata of layouts (outermost first) and page, called with assigns. Modules without metadata/1 are skipped. Returns nil when none of them has any.

tags(assigns)

Renders the <meta> and <link> tags for resolved metadata. Put it in root.html.heex, inside <head>, next to <.live_title>, which renders the title.

Attributes

  • metadata (:map) - assigns[:xenia_metadata]. Defaults to nil.