A page that reads perfectly to you can still carry a quiet instruction that keeps it out of the index, and that instruction is almost never visible in the part you are looking at. The content is fine. The signal underneath is not. That gap is the whole problem, so the fix is to stop reading the page and start inspecting what Google actually received.
Run down the usual suspects one at a time. A stray noindex tag is first: it slips in through a template, a CMS default, or a staging setting that shipped to production, and a single line in the head drops the page entirely. Next, the canonical: if it points at a different URL, a circular target, or a page that is itself noindexed, you have told Google your page is a copy of something else. Then robots.txt, which blocks the crawl before indexing is even on the table, so Google never sees the page to judge it.
Three more hide further down. JavaScript rendering can hand the crawler a blank or half-built page when the content only appears after scripts run. A soft 404 returns a 200 OK while the page is effectively empty, so Google reads it as nothing worth keeping. And near-duplication quietly folds your page into a sibling that says almost the same thing.
None of these announces itself on the surface, and there is no single universal cause to chase. The move is to inspect the page as Google fetched it, in the URL Inspection tool, and read the canonical it chose, the indexability verdict, and the rendered HTML against what you expected. The looks-fine page is lying by omission, and the list above is where it keeps its secrets. Work the suspects in order, because the cheapest ones to check, a rogue noindex or a mispointed canonical, are also the most common.