What a File-by-File Code Audit Found
An SEO audit that stops at meta tags misses most of what matters. Reading the source of a production Next.js marketplace turned up nine schema types, a sitemap index over three hand-rolled XML handlers, conditional canonicals, and a backend quirk that forced a pagination workaround.
· The RECNOS Engineering Team
Structured data as a set, not a tag
The usual audit checks whether a page has JSON-LD. The more useful question is whether the types chosen describe what the page actually is, and whether they connect to each other.
This build emits nine distinct types across the site. The interesting part is the specificity: a listing page is a `LodgingBusiness` combined with `ApartmentComplex`, carrying a postal address, coordinates, a map link, a price range, and per-room offers with a unit price specification and a monthly lease business function. That is a description of a real rental listing, not a generic `Product`.
- `WebSite` with a `SearchAction` on the homepage
- `ItemList` on listing and search pages
- `LodgingBusiness` + `ApartmentComplex` with per-room `Offer` on detail pages
- `FAQPage` generated from the same content the page displays
- `CollectionPage` with a conditional `AggregateOffer` on geo pages
- `BreadcrumbList` on detail pages
The AggregateOffer is conditional for a reason
A price range in structured data is a claim that listings exist at those prices. The geo collection pages therefore only attach an `AggregateOffer` when the count is above zero and both a low and a high price are actually known.
Omitting the node when the data is absent is the correct behaviour. An empty price range is not neutral — it is a structured-data claim that the page has no inventory, or a fabricated range if it is hardcoded.
A sitemap index over hand-rolled handlers
Rather than one sitemap, this build serves an index over three route handlers: cities, properties, and blogs. The cities handler filters to published localities and distinct intents, reusing the same predicates that gate the pages themselves — so the sitemap can never advertise a URL the gating logic would canonicalise away.
The properties handler exposed a genuine backend constraint. The API endpoint it reads from does not populate a total page count, so the handler cannot know how many pages exist. It paginates by detecting a short page instead — reading until a page comes back with fewer items than the requested size.
// The backend does not return totalPages, so paginate by
// page-size shortfall rather than by a known total.Dynamic OG images carrying live data
Three `opengraph-image` handlers generate 1200×630 PNGs at request time. The listing pages use the listing's own cover photo as the background. The locality pages render the live listing count into the card.
This is a small detail with an outsized effect on click-through. A shared link shows the actual number of PGs in that locality rather than a static brand image, which is the difference between a link that looks generated and one that looks current.
The audit's most useful output was a list of mocks
The single most valuable section of the audit was not the list of what worked. It was the register of what did not: a submission handler that is a no-op returning success, hardcoded development credentials with a deprecation marker, a placeholder image host in a fallback path, and demo payload files.
None of those are visible from the outside. All of them would be misdescribed by anyone writing up the project from its marketing copy. An audit that only records achievements produces a document that cannot be trusted on the parts that matter.