Skip to main content

guides/landing-page.md

# Complex landing page

A real landing page describes several **independent** things at once — the
organization behind the site, the site itself, a breadcrumb trail, an FAQ. The
idiomatic way to express this is a single JSON-LD document with a top-level
`@graph` array holding each node.

Pass a **list** of structs to `to_json_ld/1` (or `to_map/1`) and they are
wrapped in `@graph` automatically:

```elixir
nodes = [
  %SchemaOrg.Organization{
    name: "Acme Inc.",
    url: "https://example.com",
    logo: %SchemaOrg.ImageObject{url: "https://example.com/logo.png"},
    same_as: ["https://twitter.com/acme", "https://linkedin.com/company/acme"],
    contact_point: %SchemaOrg.ContactPoint{
      telephone: "+1-401-555-1212",
      contact_type: "customer service"
    }
  },
  %SchemaOrg.WebSite{name: "Acme", url: "https://example.com"},
  %SchemaOrg.BreadcrumbList{
    item_list_element: [
      %SchemaOrg.ListItem{position: 1, name: "Home", item: "https://example.com"},
      %SchemaOrg.ListItem{position: 2, name: "Pricing", item: "https://example.com/pricing"}
    ]
  },
  %SchemaOrg.FAQPage{
    main_entity: [
      %SchemaOrg.Question{
        name: "Is there a free trial?",
        accepted_answer: %SchemaOrg.Answer{text: "Yes, 14 days."}
      }
    ]
  }
]

SchemaOrg.to_json_ld(nodes)
```

produces:

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "name": "Acme Inc.",
      "url": "https://example.com",
      "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" },
      "sameAs": ["https://twitter.com/acme", "https://linkedin.com/company/acme"],
      "contactPoint": {
        "@type": "ContactPoint",
        "telephone": "+1-401-555-1212",
        "contactType": "customer service"
      }
    },
    { "@type": "WebSite", "name": "Acme", "url": "https://example.com" },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com" },
        { "@type": "ListItem", "position": 2, "name": "Pricing", "item": "https://example.com/pricing" }
      ]
    },
    {
      "@type": "FAQPage",
      "mainEntity": [
        {
          "@type": "Question",
          "name": "Is there a free trial?",
          "acceptedAnswer": { "@type": "Answer", "text": "Yes, 14 days." }
        }
      ]
    }
  ]
}
```

The top-level `@context` is emitted once and applies to every node in the
graph. The nodes here are **inlined**; cross-referencing shared entities by
`@id` (so the `Article` publisher points at the `Organization` node instead of
repeating it) is not yet supported — see the README roadmap.

## Embed it in a page

One `<script>` tag carries the whole graph:

```elixir
SchemaOrg.to_script_tag(nodes)   # HTML-safe <script type="application/ld+json"> string
```

In a Phoenix HEEx template (needs `:phoenix_live_view`):

```heex
<SchemaOrg.HTML.json_ld data={@nodes} />
```