You finally share your app on X, in a Slack community, or in a WhatsApp group. The preview card shows the wrong name, a blank gray box, or the logo of the tool you built it with. People scroll past.
This is common in apps built with Lovable, Bolt, Replit, v0, Cursor or Claude Code, and it is fixable in an afternoon. The same few tags control your link previews and how your pages look in Google, so one round of fixes improves both. Here are 7 steps, with copy-paste tags, AI prompts, and the official tools to refresh old previews.
Quick glossary
- Title tag: the page name in the browser tab. Google often uses it as the clickable headline in search results.
- Meta description: a one or two sentence summary of the page. Google sometimes shows it under the headline.
-
Open Graph (OG) tags:
og:title,og:description,og:imageand friends. Facebook created the standard, and LinkedIn, WhatsApp, Slack, iMessage and many other apps read it to build preview cards. -
Twitter card tags: X's own tags. X falls back to your OG tags for title, description and image, but
twitter:cardpicks the big-image card style. - Link-preview bot: the program a platform sends to read your page when someone pastes a link. Most of them do not run JavaScript.
- Cache: platforms save a preview the first time they see your link and keep showing it after you fix your page.
Step 1: See what the bots see
Before changing anything, find out what is broken. Paste your homepage URL into two free official tools:
- LinkedIn Post Inspector (linkedin.com/post-inspector): shows the title, description and image LinkedIn found.
- Facebook Sharing Debugger (developers.facebook.com/tools/debug): shows the tags Meta's crawler found, plus warnings.
Then look at the raw HTML yourself. This asks for the page the way a simple bot would and prints only the tags that matter:
curl -sL -A "facebookexternalhit/1.1" https://yourapp.com/pricing \
| grep -ioE '<title>[^<]*|<meta[^>]*(og:|twitter:|description)[^>]*>'
If the output is empty or shows the same title on every page, you found the problem. One caveat: some hosts serve pre-rendered HTML only to verified crawlers (Lovable's docs say its older React + Vite apps work this way), so curl may show an empty page while real bots get full tags. When in doubt, trust the platform debuggers.
Write down what you find in a simple table:
PAGE TITLE DESCRIPTION OG:IMAGE PROBLEM
/ "Vite + React" missing lovable.png wrong brand
/pricing "Vite + React" missing lovable.png same as home
/blog/post "Vite + React" missing none no image
Step 2: Write a unique title and description for every page you share
Google's guidance is clear: every page needs its own descriptive title, without boilerplate or keyword stuffing, and a description that accurately describes that specific page.
Start with the pages people actually share: home, pricing, your main use case or feature pages, and blog posts. Use this pattern:
Title tag: [What this page is] | [Brand]
og:title: [What this page is] (no brand; og:site_name holds it)
Description: [Who it is for] + [what they get] + [one specific detail]
A hypothetical example for a meal-planning app called PlateWise:
Home Title: Weekly meal plans for busy families | PlateWise
Description: Plan a week of dinners in 10 minutes and get one
shopping list. Free for your first 2 weeks.
Pricing Title: Pricing | PlateWise
Description: One plan, $6 a month, cancel anytime. See what is
included and how the free 2 weeks work.
A few rules from the platforms' own docs:
- Keep descriptions short. WhatsApp's docs say about 80 characters is enough for its preview, and X lists 200 characters as the maximum for its description tag. Put the important words first.
-
Leave the brand out of og:title. Apple's Messages guidance says to put the site name in
og:site_nameinstead, to avoid repeating it in the preview. - Do not let logged-in pages say "Sign In". Apple recommends that pages behind a login still describe what the link gives access to, without revealing private content.
Google may still rewrite your title or snippet in results. Good, specific text just makes that less likely.
Step 3: Add the full tag set
Here is the complete set for one page. Swap in your real values and use full https:// URLs:
<title>Pricing | PlateWise</title>
<meta name="description" content="One plan, $6 a month, cancel anytime." />
<link rel="canonical" href="https://platewise.app/pricing" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="PlateWise" />
<meta property="og:title" content="Pricing" />
<meta property="og:description" content="One plan, $6 a month, cancel anytime." />
<meta property="og:url" content="https://platewise.app/pricing" />
<meta property="og:image" content="https://platewise.app/og/pricing.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="PlateWise pricing: one plan, $6 a month" />
<meta name="twitter:card" content="summary_large_image" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
Why og:image:width and height? Facebook's docs say they let its crawler render the image immediately for the first person who shares your link.
If your app is Next.js (common with v0, Cursor and Claude Code), do not hand-write tags. Use the Metadata API. Set a base URL and a title template once in your root layout:
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://platewise.app"),
title: { template: "%s | PlateWise", default: "PlateWise" },
description: "Weekly meal plans for busy families.",
openGraph: { siteName: "PlateWise", type: "website" },
twitter: { card: "summary_large_image" },
};
Then give each page its own values:
// app/pricing/page.tsx
export const metadata = {
title: "Pricing", // becomes "Pricing | PlateWise"
description: "One plan, $6 a month, cancel anytime.",
openGraph: { title: "Pricing", url: "/pricing" },
};
metadataBase turns relative paths like /pricing into full URLs. Watch one gotcha from the Next.js docs: if a page sets openGraph, it replaces the layout's whole openGraph object, so repeat any fields you still need. For pages built from your database, such as blog posts, use generateMetadata instead. For icons, drop favicon.ico, icon.png and apple-icon.png into the app/ folder and Next.js adds the tags.
If your app is Lovable, ask in chat. Its docs say it generates titles and descriptions per page, and the page selector above the preview shows a Social card and a Search card for each page.
If your app is a plain React + Vite app (common with Bolt and Replit), the tags go in index.html, and image files go in the public/ folder, which Vite serves from the root of your site. Read Step 5 before you rely on this for more than one page.
Step 4: Make one share image that works everywhere
Each platform has its own rules. Here is what their docs say:
PLATFORM SIZE / RATIO FILE LIMIT NOTES
Facebook 1200x630 best, 1.91:1 8 MB under 600x315 = small thumbnail
LinkedIn at least 1200x627, 1.91:1 5 MB under 401 px wide = thumbnail
X 2:1, min 300x157 5 MB JPG, PNG, WEBP, GIF; no SVG
WhatsApp 300 px wide or more, max 4:1 under 600 KB tags in first 300 KB of HTML
iMessage 900 px wide or more 10 MB total avoid small text in the image
The safe choice that fits all of them: 1200 x 630 pixels, PNG or JPG, under 300 KB. Lovable's docs warn that WhatsApp shows no preview for larger files.
Design tips:
- Keep the important content in the middle. X uses 2:1 and the others use 1.91:1, so the edges may get cropped.
- Use your product name and one short promise in big text, or a clean screenshot. Apple warns that small text can become unreadable.
- Never reuse your logo or favicon as the share image. Small square images become tiny thumbnails.
Ask your AI tool to build it:
Create a 1200x630 PNG share image for my app [name]. Show the product
name and this promise in large text: "[one short promise]". Keep all
text inside the middle 1000x500 area. Use my brand colors [colors].
Save it as public/og/home.png, keep it under 300 KB, and set og:image
and the image width, height and alt tags to its full https URL.
Next.js users can also place an opengraph-image.png file inside any route folder and Next.js will add the image tags for that route automatically.
Step 5: Make sure the tags exist before JavaScript runs
This is the step that breaks most vibe-coded apps. Many AI-built apps are single-page apps: the server sends one nearly empty HTML file, and JavaScript builds the page in the browser. Link-preview bots usually do not wait for that. Apple's docs state plainly that link previews in Messages do not run JavaScript. Slack's link bot fetches as little of the page as it can. WhatsApp needs the tags within the first 300 KB of HTML.
So if your AI tool added tags with a library like react-helmet, browsers see them but bots do not. Every page previews with whatever is in index.html.
How to fix it depends on your stack:
-
Next.js: the Metadata API from Step 3 already puts tags in the server HTML. Keep
metadataandgenerateMetadatain Server Components, as the docs require. - Lovable: its docs say new apps use server-side rendering, and older React + Vite apps get pre-rendered pages for verified crawlers, including social-preview bots. You mostly need good per-page tags.
-
Plain Vite SPA (Bolt, Replit, others): set strong site-wide defaults in
index.htmlso every link at least looks right. For pages you want to share with their own preview, such as pricing, features and blog posts, serve them as real HTML: a static marketing site, a server-rendered framework, or a build-time pre-render step.
A prompt to plan this safely:
My app is a client-side React app. Social link previews only read the
first HTML response and do not run JavaScript. List every public page
I would want to share. For each, tell me whether its title, description
and og:image are in the raw HTML today. Then propose the smallest change
that puts unique tags in the server response for those pages only.
Do not change logged-in app pages. Show me the plan before editing.
Step 6: Clear the old previews
After you deploy, refresh each platform:
- Facebook (and Meta apps): open the Sharing Debugger and click Scrape Again. Facebook caches images by URL, so give a changed image a new file name or URL. Old posts keep their old preview.
- LinkedIn: paste the URL into Post Inspector. It shows when LinkedIn last updated its data and lets you request a re-scrape.
-
X: the Card Validator lost its preview feature in 2022, and X's docs said cards are re-read roughly every seven days. Check a card by pasting the link into the post composer. X's troubleshooting docs suggested adding a parameter to the image URL, such as
og.png?v=2, so the new image is fetched. - Slack: Slack's docs say responses are cached for around 30 minutes, so just wait and post again.
-
WhatsApp: there is no official refresh tool. Its docs suggest typing the link into a chat and waiting for the preview above the text box. If it is stale, testing with a harmless parameter like
?v=2is a common workaround.
Step 7: Check every page, then keep it from breaking
One fixed homepage is not enough. Check every public page for duplicate or missing titles, descriptions and images, using a crawler instead of one URL at a time. Disclosure: I work on Siteory. Siteory (siteory.co) is one free option: it reads your public HTML and flags missing titles and meta descriptions, and duplicate titles, across the pages it crawls. It does not draw social cards for you, so pair it with the platform debuggers from Step 1.
Also check:
- Favicon: Google needs a square icon (larger than 48x48 px recommended) at a stable URL, linked from your homepage. It must not be blocked from crawling.
- robots.txt: do not block Twitterbot or the folder that holds your images.
- Staging links: previews only work for public pages. A password-protected preview URL will always show a blank card.
Paste this audit prompt into your AI tool after every big change:
Check every public route in this project. For each route, list the
title, meta description, og:title, og:description, og:image (full URL)
and twitter:card. Flag: duplicate titles, missing descriptions,
relative or missing image URLs, images that are not 1200x630, and tags
that are only added by client-side JavaScript. Do not edit anything yet.
Common mistakes
- Leaving the template defaults. "Vite + React", "Lovable App" or "Your SaaS Name" in the title or preview tells people the app is unfinished.
-
Relative image URLs.
/og.pngworks in your browser but not for bots. Usehttps://yourapp.com/og.png. - Same title everywhere. Your pages look identical in Google and in chats.
- Tags added by JavaScript only. They are invisible to most preview bots.
- Huge or SVG images. X does not support SVG, and WhatsApp needs under 600 KB.
- Forgetting the cache. Run the debuggers after every change.
- Testing only on one platform. A card that works on LinkedIn can still fail on WhatsApp.
Your action summary
- Run Post Inspector, the Sharing Debugger and the curl check on your top pages.
- Write a unique title and description for home, pricing, feature pages and posts.
- Add the full tag set, or use the Next.js Metadata API or Lovable's Social card.
- Create a 1200x630 PNG or JPG share image under 300 KB.
- Make sure the tags are in the first HTML response, not added by JavaScript.
- Refresh caches on Facebook, LinkedIn and X, and retest Slack and WhatsApp.
- Crawl every public page for duplicates and gaps, and rerun the audit prompt after big changes.
Your link preview is often the first thing a stranger sees of your product. Spend one afternoon on it, and every link you share from now on will look like it came from a real, finished product.












