sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git

docs/content/templates/social.md (3160B)

      1 +++
      2 title = "Social cards and structured data"
      3 description = "Markup you own, from values that already exist"
      4 weight = 50
      5 toc = true
      6 +++
      7 
      8 sqzass injects nothing into `<head>`. There is no injection point and there will
      9 not be one — a generator that quietly adds tags is a generator you cannot fully
     10 read the output of. Everything below is markup you put in your own `base.html`,
     11 built from values already in the template context.
     12 
     13 ## OpenGraph and Twitter
     14 
     15 Without these, every page of your site renders as a bare URL in Slack, Discord
     16 and KakaoTalk.
     17 
     18 ```html
     19 <meta property="og:type" content="{{ "website" if page.is_section else "article" }}">
     20 <meta property="og:site_name" content="{{ site.title }}">
     21 <meta property="og:title" content="{{ page.title }}">
     22 <meta property="og:url" content="{{ page.permalink }}">
     23 <meta property="og:locale" content="{{ "ko_KR" if page.language == "ko" else "en_US" }}">
     24 {%- if page.description %}
     25 <meta property="og:description" content="{{ page.description }}">
     26 {%- endif %}
     27 <meta name="twitter:card" content="summary">
     28 ```
     29 
     30 `summary`, not `summary_large_image` — the large variant needs an image, and
     31 declaring it without one produces an empty box rather than a nicer card. If you
     32 have a per-page image, put it in front matter and switch:
     33 
     34 ```toml
     35 +++
     36 title = "Installation"
     37 [extra]
     38 image = "/images/install.png"
     39 +++
     40 ```
     41 
     42 ```html
     43 {%- if page.extra.image %}
     44 <meta property="og:image" content="{{ site.origin }}{{ page.extra.image }}">
     45 <meta name="twitter:card" content="summary_large_image">
     46 {%- else %}
     47 <meta name="twitter:card" content="summary">
     48 {%- endif %}
     49 ```
     50 
     51 `og:image` must be absolute, which is what `site.origin` is for.
     52 
     53 ## Breadcrumbs as JSON-LD
     54 
     55 This is the one piece of structured data that visibly changes a Google result
     56 for a documentation site: the result shows `Home › Writing content › Front matter`
     57 instead of a bare URL.
     58 
     59 ```html
     60 {%- if page.section %}
     61 <script type="application/ld+json">
     62 {
     63   "@context": "https://schema.org",
     64   "@type": "BreadcrumbList",
     65   "itemListElement": [
     66     {"@type": "ListItem", "position": 1, "name": "{{ site.title }}", "item": "{{ site.origin }}{{ site.base_path }}/"},
     67     {"@type": "ListItem", "position": 2, "name": "{{ page.section.title }}", "item": "{{ site.origin }}{{ page.section.url }}"},
     68     {"@type": "ListItem", "position": 3, "name": "{{ page.title }}", "item": "{{ page.permalink }}"}
     69   ]
     70 }
     71 </script>
     72 {%- endif %}
     73 ```
     74 
     75 Note the `{%- if page.section %}`: top-level pages have no section, and a
     76 breadcrumb with a missing rung is worse than none.
     77 
     78 > [!WARNING]
     79 > This block is inside `<script>`, where HTML escaping is wrong — a title
     80 > containing `"` produces invalid JSON. Keep titles free of quotes, or drop the
     81 > structured data rather than shipping JSON that silently fails to parse. This
     82 > is the one place in a template where our escaping does not protect you.
     83 
     84 ## Site-level
     85 
     86 ```html
     87 <script type="application/ld+json">
     88 {
     89   "@context": "https://schema.org",
     90   "@type": "WebSite",
     91   "name": "{{ site.title }}",
     92   "url": "{{ site.origin }}{{ site.base_path }}/"
     93 }
     94 </script>
     95 ```
     96 
     97 One block, on every page, is enough. Search engines read it once.