The tags in your source are not necessarily the tags being served
A crawler fetches the URL, reads the HTML it receives, and stops. It does not run your JavaScript, so tags injected client-side by a framework or a tag manager are invisible to it. It does not log in, so a page behind authentication returns whatever the logged-out response is. It does not follow a chain of redirects indefinitely, and a canonical pointing elsewhere changes which page it considers.
That gap is where most blank cards come from. The tags are right in the repository, right in the rendered DOM, right in the view-source of the page when you check it while signed in - and absent from the response a crawler receives. Fetching the URL the way a crawler does is the only check that answers the actual question.
The image is the part that fails
A preview image has to be reachable by a stranger, at an absolute URL, over HTTPS, with a content type that says image, at a size the platform will accept. Break any one of those and the card renders without it. Relative paths are the most common mistake, followed by images behind a CDN rule that blocks unknown user agents, followed by files large enough that the platform gives up fetching them.
Dimensions matter too. Around 1200 by 630 is the safe ratio for the large card format, and an image far from that gets cropped unpredictably or demoted to a small square. If the card shows a title and description but no picture, the image is almost always the thing to check rather than the tags.
Why the old preview persists after you fix it
Social platforms cache what they scraped the first time a link was shared, sometimes for a long time. Fixing the tags does not update that cache, which is why a link that was shared once while broken keeps showing the broken version everywhere including in your own test message.
No third-party tool can clear it. The platforms provide their own debuggers that re-scrape a URL on demand, and that is the only thing that works - each platform separately, for each URL you care about. What a checker like this tells you is whether the re-scrape will find something correct when you trigger it.