AI links open with a title + excerpt (these tools can't fetch the page themselves) — use "Copy full article" to paste the complete text for a fuller summary.
Introduction
This article demonstrates how we added share buttons to each blog post, and the Open Graph tags that control how the link appears once somebody shares it.
Those are two halves of one job. The buttons get the link out. The tags decide whether it looks like an article or like a bare URL.
Share Links Are Just URLs
No SDK, no script, no tracking pixel. Every network accepts a share URL with your page address in the query string.
https://www.facebook.com/sharer/sharer.php?u=<url>
https://wa.me/?text=<title + url>
https://t.me/share/url?url=<url>&text=<title>
mailto:?subject=<title>&body=<url>
So each button is an anchor.
<a [href]="shareLinks()?.whatsapp" target="_blank" rel="noopener noreferrer"
class="btn-icon hover:!bg-[#25D366] hover:!border-[#25D366] hover:!text-white"
aria-label="Share on WhatsApp" title="Share on WhatsApp">
shareLinks() is a computed signal built from the post, so the URLs appear when the post loads.
Features
- No third-party scripts, so no performance or privacy cost.
- Brand colour on hover, neutral at rest.
- A fixed rail on wide screens, an inline row on small ones.
- Works with the page's own Open Graph tags.
With the following steps, you can add your own.
- Build the share URLs.
- Place them responsively.
- Handle the network with no share URL.
- Set the Open Graph tags.
Build the Share URLs
Two rules cover almost everything.
Encode the values. A post title with a & or a ? in it will break the query string of the share URL. Use encodeURIComponent on every part you interpolate.
Use the absolute canonical URL. Not router.url, not a relative path. The network fetches whatever you give it, and a relative path gives them nothing.
rel="noopener noreferrer" again on every target="_blank".
The icons are inline SVG rather than an icon font or an image — they scale, they take the current colour, and they cost no extra request.
The hover colour is the network's own brand colour, applied only on hover.
class="btn-icon hover:!bg-[#1877F2] hover:!border-[#1877F2] hover:!text-white"
At rest the buttons are neutral and match the site. On hover they identify themselves. A rest state of five saturated brand colours competes with your own content — this gets the recognition without the noise.
The ! is Tailwind's important modifier, needed here because .btn-icon already sets those properties from the design system.
Place Them Responsively
Two placements, chosen by breakpoint.
<!-- Desktop: fixed rail in the left gutter -->
<div class="hidden xl:flex flex-col gap-3 fixed top-1/2 -translate-y-1/2 left-[calc(50%-27rem)] z-10">
<!-- Mobile and tablet: inline row above the article -->
<div class="xl:hidden flex items-center gap-2.5 mb-8">
The desktop rail uses left-[calc(50%-27rem)], which places it relative to the centred article column rather than the window edge. So it sits just outside the text at any window width, instead of drifting into the far corner on a wide monitor.
top-1/2 with -translate-y-1/2 centres it vertically. fixed keeps it there while reading.
Note the breakpoint is xl, not md. The rail needs real gutter space beside the article. At md the article is already using most of the width and the rail would overlap the text. Choose this breakpoint by looking at your own layout, not by copying ours.
Below xl the same links render as a row above the article, where they cost nothing and are still findable.
Handle the Network With No Share URL
Instagram has no web share URL. There is no instagram.com/share?url= — the platform does not support sharing a link into it from a browser.
So that one is a button, not a link.
<button type="button" (click)="shareViaInstagram()" id="share-instagram"
class="btn-icon hover:!text-white hover:!border-transparent
hover:![background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)]"
aria-label="Share via Instagram" title="Copy link for Instagram">
It copies the link to the clipboard, so the reader can paste it into a story or a bio.
The title says "Copy link for Instagram", not "Share on Instagram". That is deliberate — the tooltip tells the truth about what the button does, so nobody clicks expecting Instagram to open.
This is a small case of a general rule. When one item in a row cannot do what the others do, do not fake it and do not hide it. Give it the nearest honest behaviour and label it accurately.
Set the Open Graph Tags
The buttons are half the feature. What the shared link looks like is the other half, and that comes from meta tags on your page.
<meta property="og:title" content="My Site — Modern Web Platform">
<meta property="og:description" content="Scalable web platform built with Angular and Azure Static Web Apps.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://www.example.com/">
<meta property="og:image" content="https://www.example.com/favicon.ico">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="My Site — Modern Web Platform">
<meta name="twitter:description" content="Scalable web platform built with Angular and Azure Static Web Apps.">
Those are the site defaults in index.html. Per-post tags are set by a SeoService as each post loads.
One thing to get right: these must be in the prerendered HTML. A social network's crawler fetches your page and reads the HTML. It does not run Angular. If your tags are only set at runtime by a service, the crawler sees the defaults and every shared post looks identical.
We prerender every blog route at build time, so the per-post tags are baked into the file the crawler receives. If your app is client-rendered, this is the piece that will not work, and no amount of Meta service code fixes it.
And one honest gap in ours. og:image points at favicon.ico. That is a tiny square icon where a 1200×630 preview image should be, while twitter:card promises summary_large_image. So shared links render worse than they should.
The fix is a proper OG image per post, or at least one good site-wide default at 1200×630. It is on the list. I am pointing at it because it is a very easy thing to leave half-done — the tags are present, the validator does not complain loudly, and the result just looks a bit poor forever.
Check Your Work
Do not guess. Every network has a validator that shows exactly what its crawler sees.
- Facebook: Sharing Debugger
- X/Twitter: Card Validator
- LinkedIn: Post Inspector
These also let you force a re-scrape, which matters because networks cache aggressively. Fix your tags and the old preview can persist for days until you clear it.
Conclusion
In this article we learned how to build share buttons from plain URLs with no SDKs, position them as a fixed rail on wide screens and an inline row below that, handle Instagram honestly with a copy-link button, and set the Open Graph tags that decide how the shared link looks — including why those tags must be in your prerendered HTML.
The buttons are the easy half. The preview is the half people actually see.
Reference
Comments
Be the first to comment.