Carnegie Mellon Web Kilt

Tailored to the CMU web template

Link Previews

Give shared links a clear title, a short description and a recognizable image.

What People See

When someone shares your page in Slack, Teams or a social post, its preview helps people decide whether to open it. The app builds that preview from information in the page's HTML.

  • Title: Name the page and the site it belongs to.
  • Description: Say what people will find in one short sentence.
  • Image: Show the site, usually the top of its home page; see The Preview Image.

The app controls the layout, so the same link may look different in each one.

To show a preview on a page of your own, such as in a guide or a tool that checks links, put a cmu-link-preview in a screenshot frame: the image, then a cmu-link-preview-text with the site, the title in <strong> and the description.

cmu-link-preview, cmu-link-preview-text

An illustrative preview using this guide's image.

The Preview Image

Every site should have one image for its previews. Unless a page has a better picture of its own, use the top of the site's home page:

  • What it shows: The home page at 1200 pixels wide, cropped to the top 630, roof and site name included, with the menu closed and no one signed in.
  • Size and type: 1200 × 630 pixels, a JPEG under 300 KB.
  • Name and place: images/preview.jpg in the site's top folder, so it's at /images/preview.jpg on the site. For a site built from a repository, commit it there, beside the pages.
  • Alt text: Name the site and say what the page shows, such as “Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.”
  • Nothing private: No one's name, data or messages. Use sample data if the page shows any.
  • Only what you can share without a credit: A preview shows no caption, so swap out any photo whose license asks for one.

To make it, use your browser's developer tools: most can set the page to an exact size and take a screenshot of it. Set 1200 × 630, capture the page and save it as a JPEG. Retake it when the home page changes.

Open Graph Tags Template

Open Graph tags describe the page for apps that generate previews. Add them to the page's <head>. This example is for an equipment reservation page:

<meta property="og:site_name" content="Equipment Checkout">
<meta property="og:title" content="Reserve a Camera Kit - Equipment Checkout">
<meta property="og:description" content="Cameras, laptops and lights, free for CMU courses.">
<meta property="og:url" content="https://www.cmu.edu/equipment/reserve.html">
<meta property="og:type" content="website">
<meta property="og:image" content="https://www.cmu.edu/equipment/images/preview.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.">
<meta name="twitter:card" content="summary_large_image">

In Cascade

CMU's Cascade template writes each page's title, description, address and type for you, from the page's settings; fill in the page's Description field. Don't add those tags yourself: a second copy conflicts. To add the picture:

  1. Make the site's preview image.
  2. Upload it to the site's images folder as preview.jpg.
  3. Open the site's setup block, select Edit and choose the image under Social Media Image. That's the default for every page; a page with a better picture of its own can set its own Social Media Image. Cascade suggests 900 × 600, but 1200 × 630 is what the apps that build previews expect, so they don't crop it.
  4. Publish the site's pages, then check a preview, and look for og:image in a page's source.

In Another CMS

Check its link-preview or social-sharing settings first; most write some of these tags from each page's title and summary. Add only the tags it doesn't already provide, in the page template's <head>, so every page gets them.

In Rails

Put the tags in the layout, filled in from each view, and set the app's public address once (config.x.public_url). A view names itself with <% content_for :title, "Reserve a Camera Kit" %> and <% content_for :description, "…" %>. The image goes in public/images/preview.jpg.

<%# app/views/layouts/application.html.erb, in <head> %>
<% site = "Equipment Checkout" %>
<% base = Rails.configuration.x.public_url %>
<meta property="og:site_name" content="<%= site %>">
<meta property="og:title" content="<%= content_for?(:title) ? "#{yield(:title)} - #{site}" : site %>">
<meta property="og:description" content="<%= content_for?(:description) ? yield(:description) : "Cameras, laptops and lights, free for CMU courses." %>">
<meta property="og:url" content="<%= base + request.path %>">
<meta property="og:type" content="website">
<meta property="og:image" content="<%= base %>/images/preview.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.">
<meta name="twitter:card" content="summary_large_image">

In Django

Put the tags in the base template, filled in from each view's context (page_title, page_description), with the app's public address as a setting (PUBLIC_URL) that a context processor passes to every template. The image goes in your static files.

{# templates/base.html, in <head>; {% load static %} at the top #}
<meta property="og:site_name" content="Equipment Checkout">
<meta property="og:title" content="{% if page_title %}{{ page_title }} - {% endif %}Equipment Checkout">
<meta property="og:description" content="{{ page_description|default:'Cameras, laptops and lights, free for CMU courses.' }}">
<meta property="og:url" content="{{ PUBLIC_URL }}{{ request.path }}">
<meta property="og:type" content="website">
<meta property="og:image" content="{{ PUBLIC_URL }}{% static 'images/preview.jpg' %}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.">
<meta name="twitter:card" content="summary_large_image">

In SvelteKit

SvelteKit renders pages on the server, so tags in <svelte:head> reach the apps that build previews. Put the shared tags in the root layout and let each page add its own title and description in its own <svelte:head>. The image goes in static/images.

<!-- src/routes/+layout.svelte -->
<script>
  import { page } from "$app/state";
  const base = "https://equipment.andrew.cmu.edu";
  let { children } = $props();
</script>
<svelte:head>
  <meta property="og:site_name" content="Equipment Checkout" />
  <meta property="og:url" content={base + page.url.pathname} />
  <meta property="og:type" content="website" />
  <meta property="og:image" content="{base}/images/preview.jpg" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
  <meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit." />
  <meta name="twitter:card" content="summary_large_image" />
</svelte:head>
{@render children()}

<!-- src/routes/reserve/+page.svelte -->
<svelte:head>
  <meta property="og:title" content="Reserve a Camera Kit - Equipment Checkout" />
  <meta property="og:description" content="Cameras, laptops and lights, free for CMU courses." />
</svelte:head>

In React

The apps that build previews don't run JavaScript, so tags a React component adds in the browser never reach them. In an app that renders only in the browser, put the site's tags in index.html, the same for every page: the site's name, description, home address and preview image. For tags that differ by page, render the page on the server with a framework built on React, like Next.js, which writes the <head> there.

<!-- index.html, in <head>: the same for every page -->
<meta property="og:site_name" content="Equipment Checkout">
<meta property="og:title" content="Equipment Checkout">
<meta property="og:description" content="Cameras, laptops and lights, free for CMU courses.">
<meta property="og:url" content="https://equipment.andrew.cmu.edu/">
<meta property="og:type" content="website">
<meta property="og:image" content="https://equipment.andrew.cmu.edu/images/preview.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.">
<meta name="twitter:card" content="summary_large_image">

In Vue

The apps that build previews don't run JavaScript, so tags a Vue component adds in the browser never reach them. In an app that renders only in the browser, put the site's tags in index.html, the same for every page: the site's name, description, home address and preview image. For tags that differ by page, render the page on the server with Nuxt, which writes the <head> there.

<!-- index.html, in <head>: the same for every page -->
<meta property="og:site_name" content="Equipment Checkout">
<meta property="og:title" content="Equipment Checkout">
<meta property="og:description" content="Cameras, laptops and lights, free for CMU courses.">
<meta property="og:url" content="https://equipment.andrew.cmu.edu/">
<meta property="og:type" content="website">
<meta property="og:image" content="https://equipment.andrew.cmu.edu/images/preview.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Equipment Checkout's home page: Reserve a Camera Kit, beside a photo of a camera kit.">
<meta name="twitter:card" content="summary_large_image">
  • Keep the title specific: Use the page's name followed by the site's name, as in its <title>.
  • Keep the description short: Use the same sentence as the page's <meta name="description">. Put the useful information first; apps may shorten it.
  • Use full addresses: Give both the page and image a public https:// address, not a path such as /images/preview.jpg.
  • Use the preview image: The site's images/preview.jpg, with its size and alt text.
  • Request a large image: twitter:card with summary_large_image requests the larger preview on platforms that support Twitter Cards.

In an App

An app writes every tag itself, and its previews are only as good as its layout. Put the tags in the one layout every page uses, filled in from each page, so no page goes out without them.

  • Give every page a description: Make it part of what a page must set, as a title is, and use the app's own one-sentence description as the fallback.
  • Build addresses from one setting: Keep the app's public address, such as https://equipment.andrew.cmu.edu, in its configuration and join each page's path to it. Don't use the address a request came in on: behind a proxy or on a test copy, it's the wrong one.
  • Serve the image from the app: Put images/preview.jpg with its other static files, open to everyone, so it lasts as long as the app and loads without signing in.
  • Plan for sign-in: The apps that build previews aren't signed in. A link to a page behind sign-in shows whatever a signed-out visitor gets, usually the sign-in page, so give that page the app's own title, description and image.
  • Never put a user's data in the tags: No names, course numbers or anything else from the signed-in page, even where it would be more specific.
  • Check them automatically: A test that fetches a few pages and looks for og:title, og:description and og:image catches a layout that lost them. These docs write and check theirs with a script.

News Stories and Posts

Most pages are website: a home page, a service or help page, a form or an app's pages. Checkers such as LinkedIn's report “No author found” and “No publication date found” for them, which is fine: those fields are for articles.

A page that's dated and written by someone is an article: a news story, a blog post, an announcement or a recap of an event. Give it the type, its date and its author, so previews can show when it was published:

<meta property="og:type" content="article">
<meta property="article:published_time" content="2027-01-11T09:00:00-05:00">
<meta property="article:modified_time" content="2027-01-12T14:30:00-05:00">
<meta property="article:author" content="Margaret Morrison">
<meta name="author" content="Margaret Morrison">
  • Give the time and time zone: Write the date as above, with the offset from UTC, -05:00 in winter and -04:00 in summer.
  • Add the modified time only after a real change, such as a correction, not every time the page is published.
  • Put them in the template for stories, such as a CMS's news or blog page type or an app's post layout, so every story gets them without anyone remembering.

Check a Preview

  • Check the published page, signed out: Paste its address into a message to yourself in Slack or Teams. Check the title, description and image.
  • Allow time for updates: Apps cache previews, so changes may not appear immediately. Use LinkedIn's Post Inspector to check what LinkedIn sees.