Back to Ideas 6 min read

What the Link Looks Like Before Anyone Clicks

E
EkoHacks Team
·

Most people who ever see a page of ours will see it first as a link: in a chat, in a feed, in a message from a friend. What that link looks like is decided by a handful of tags in the page's head, read once by whatever app is showing the link, and never by the reader. Day zero found that none of our sixty two routes carried an image tag, and every one of them asked for the small card. Share any page and you got a rectangle of text.

The tag is the page

The reframe here is small and it matters. A share preview is not decoration added after the fact by a marketing step. It is the first rendering of the page, made by somebody else's software from tags we ship, and it is produced by the same build that produces everything else. If the build does not put an image in the head, there is no image. If it puts a relative path there, the app reading it has nothing to resolve it against and shows nothing. The preview is part of the page, and it is checkable the same way the rest of the page is.

What we had

Three posts on the live site carried a cover image. Forty five more covers had been drawn over the summer, dark isometric scenes, one for each post, and they sat on one laptop, referenced by one line of frontmatter each that had never been committed. Today they went in, one commit, ninety files, forty five added lines. A cover that is not in the repository is not on the site, for the same reason a page the build does not produce does not exist.

The landing pages, the legal pages and the posts that have no cover needed something too. We drew a default card from the brand: the navy from our tokens, the white logo, the tagline from the home page, in a script that lives in the repository and can be run again if the brand changes.

The default share card: the EkoHacks logo on deep navy, with the line "Employable software engineering, taught through live projects"

The change

All of it went into the one composable every page already calls for its title and description. A page passes the site relative path of its image if it has one; the composable makes it absolute, falls back to the default card, asks for the large card format, and says whether the page is an article:

export const DEFAULT_OG_IMAGE = "/images/og-default.jpg";

export function absoluteImageUrl(path?: string) {
  const p = path && path.trim() ? path.trim() : DEFAULT_OG_IMAGE;
  return /^https?:\/\//.test(p) ? p : `${SITE_URL}${p.startsWith("/") ? "" : "/"}${p}`;
}

useServerSeoMeta({
  // ... title, description, canonical as before
  ogType,
  ogImage: image,
  twitterCard: "summary_large_image",
  twitterImage: image,
});

The post page passes its cover and declares itself an article. Everything else gets the default and is a website. Before, the head of a post read like this:

<meta property="og:title" content="Landmines">
<meta property="og:description" content="...">
<meta name="twitter:card" content="summary">

After:

<link rel="canonical" href="https://ekohacks.com/blog/landmines">
<meta property="og:url" content="https://ekohacks.com/blog/landmines">
<meta property="og:type" content="article">
<meta property="og:image" content="https://ekohacks.com/images/blog/landmines-hero.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://ekohacks.com/images/blog/landmines-hero.jpg">

The check

The live check from two posts ago now asks three more questions of every page: is there an image tag, is it an absolute address on our own origin, and does the image it names actually answer 200. That last one is asked once per image rather than once per page, and it is the question that would have caught the forty five covers on a laptop, had the tag existed to point at them.

Run against production before the deploy:

https://ekohacks.com: 71 addresses checked, 132 wrong
  /blog has no og:image
  /blog twitter:card is not summary_large_image
  ...

Two faults per page, sixty six pages. The build was checked offline before it went anywhere: sixty six pages with an image, forty eight distinct images all present in the output, nineteen pages on the default card, fifty four posts typed as articles.

After the deploy, against production:

https://ekohacks.com: 120 addresses checked, 2 wrong
  /blog/what-the-link-looks-like-before-anyone-clicks answered 404
  sitemap is missing https://ekohacks.com/blog/what-the-link-looks-like-before-anyone-clicks

One hundred and twenty questions now, because every distinct image is asked once. The two wrong answers are this post, which was not yet committed when the check ran, and which the map correctly did not know about. Every page that existed had an image, every image answered.

What this does not fix

Two honest limits. The apps that render previews cache them, some for days, so a link to an old post that someone shared last month may keep its bare rectangle until their cache lets go. We cannot speed that up. And the covers are 1264 by 848, a three by two frame, where most preview cards are closer to two by one. The apps crop. We looked at regenerating forty eight images to the wider frame and decided the crop was acceptable and the afternoon was not, which is a decision we may revisit when the measurement post tells us whether anyone shares these links at all.

The number for today: sixty six pages, forty eight images, one hundred and thirty two missing tags on the morning run and none on the evening one.

E

Written by

EkoHacks Team

More from Ideas

·6 min read

What the Nullable Gave Back

One file, seven behaviours held fixed, the database swapped for a Nullable: about 180 times less time inside the tests, and coverage flat to two decimals.

E
EkoHacks Team
·6 min read

Twenty Six More Tests, Four Fewer Behaviours

Removing the mocks grew the suite from 44 tests to 70 and quietly deleted four behaviours, every one of them a failure path. Test count is not coverage.

E
EkoHacks Team
·6 min read

The Best Coverage Number in the Room

Same commit, same spec, same test count. The mocked suite ran 5.6 times faster, covered 3.5 fewer points of real code, and posted the best branch coverage.

E
EkoHacks Team