Live crawl of all 30 sitemap URLs on production, plus the Search Console “Blocked due to unauthorized request (401)” investigation. Every number below was measured against the deployed site, not estimated.
I probed every sitemap path plus /admin, /portal, /studio and
/api/* on production: nothing on boommedia.us returns 401. The 401s come from
*.vercel.app deployment URLs. Vercel Deployment Protection is enabled, so those hosts 302 to
vercel.com/sso-api?url=… — an unauthorized response from Google’s perspective.
boommediaus-boom-media.vercel.app → 302 to Vercel SSO loginboommediaus-git-main-boom-media.vercel.app → 302 to Vercel SSO login (same under Googlebot UA)boommediaus-<hash>-boom-media.vercel.app → 302 to Vercel SSO loginShipped in commit 922f09c:
// src/app/robots.ts — disallow-all on any non-production deployment
if (process.env.VERCEL_ENV && process.env.VERCEL_ENV !== "production") {
return { rules: [{ userAgent: "*", disallow: "/" }] };
}
// production rules now also disallow /admin/
Preview deployments additionally send X-Robots-Tag: noindex, nofollow via
next.config.ts. Once Google recrawls, the 401 bucket drains on its own.
*.vercel.app. If any are on boommedia.us itself, send me the list — that would be a
different cause than the one fixed here. Then use Validate Fix to trigger recrawl.
The entire form sits inside a <Suspense> boundary because it calls
useSearchParams(). Next.js therefore prerenders the fallback — an empty
<div style="min-height:100vh"> — and the page only exists after hydration. Googlebot does
render JavaScript, but on a deferred second pass, and every other page on the site serves complete HTML on
first response.
<h1> — the only such page.Fix: move the useSearchParams() read into a small
child component and leave the form markup outside the Suspense boundary, so step 1 prerenders.
src/app/layout.tsx sets template: "%s — Boom Media", but most pages already end their
own title with | Boom Media or | BOOM Media. The result is a doubled brand and titles up
to 96 characters — Google shows roughly 60.
/restaurants
Restaurant Solutions — Online Ordering, POS, Digital Signage & SEO | BOOM Media — Boom Media
96 chars — everything past ~60 is dropped in the SERP
/restaurants 96 chars/gloriafood-replacement 91 chars/restaurants/ordering 86 chars/restaurants/pos 84 chars/compliee 84 chars/services 83 chars/restaurants/online-ordering-costs 83 chars/about 80 charsFix: strip the hand-written | Boom Media suffix
from each page’s metadata.title and let the template add it once.
ComparisonTable on /blog/boom-vs-competitors
ComparisonTable is not a schema.org type. Google ignores the node, and an unrecognised
@type in a graph can suppress rich-result eligibility for the valid
BlogPosting markup alongside it. Replace it with a Table node, or model the
comparison as an ItemList of Product/Service entries.
Google truncates around 155–165 chars, so the call-to-action at the end is cut. The longest is
251 chars on /boom-online-ordering.
/boom-online-ordering 251 chars/restaurants 200 chars/restaurants/online-ordering-costs 200 chars/blog/boom-vs-competitors 193 chars/gloriafood-replacement 192 chars/restaurants/ordering 185 chars/bar 185 chars/service-industry 184 chars/restaurants/pos 183 chars/investment 183 charsThese return 200 and are allowed by robots.txt, but src/app/sitemap.ts never lists them,
so they rely entirely on internal links for discovery. Corrected upward from an earlier count of 4:
that figure came from reading static route folders only, which missed every page generated by the
blog/[slug] and services/[slug] dynamic routes. A full link crawl found 31.
/services/online-ordering, /services/local-seo,
/services/pos-systems, /services/website-design and 16 more/blog/pos-comparison-oracle-square-toast and
/blog/google-business-profile-checklist/no-wordpress — a full service page with a comparison table/maintenance, /restaurant-blog, /ads-100-budgetThe 20 /services/* pages are the real story here — that is an
entire commercial section of the site left out of the sitemap.
Decide per page: add to the sitemap, or noindex it if it is a
landing page you do not want in the index.
og:imageLinks to these pages render as bare text cards when shared on LinkedIn, Facebook or iMessage — including two of your highest-intent commercial pages.
/restaurants/ordering /restaurants/pos /boom-online-ordering /gloriafood-replacement /blog Not automatically a problem — /contact and /seo-audit are tools, not
articles. But /websites, /hosting and /restaurants/pos are commercial pages
competing on head terms, and they are thin for that job.
/contact 107 words/seo-audit 233 words/blog 358 words/restaurants/pos 363 words/websites 374 words/hosting 383 wordsproject-t4qis.vercel.app serves the full production site at 200
Unlike the other *.vercel.app aliases, this one is not behind Deployment Protection — it serves
every page, and its robots.txt says Allow: /. The canonical tags correctly point at
https://boommedia.us, which is what keeps this from being a live duplicate-content problem, but the
alias is unnecessary exposure. Remove it in Vercel → Project → Domains if nothing depends on it.
lang="en", a viewport tag, and explicit index, follow.LocalBusiness + ProfessionalService schema on all 30 pages, with FAQPage on 3 and HowTo on 1.next.config.ts.| Path | Status | TTFB | Title | Desc | H1 | Words | Alt | Schema | Flags |
|---|---|---|---|---|---|---|---|---|---|
/ |
200 | 429 | 54 | 148 | 1 | 1404 | 3/3 | 1 | clean |
/about |
200 | 238 | 80 | 156 | 1 | 880 | 3/3 | 1 | title 80 |
/contact |
200 | 233 | 45 | 161 | 0 | 107 | 2/2 | 1 | h1 ×0 107w |
/portfolio |
200 | 279 | 42 | 153 | 1 | 585 | 2/2 | 1 | clean |
/guarantee |
200 | 174 | 54 | 148 | 1 | 840 | 2/2 | 1 | clean |
/services |
200 | 244 | 83 | 147 | 1 | 461 | 2/2 | 1 | title 83 |
/websites |
200 | 223 | 54 | 148 | 1 | 374 | 2/2 | 1 | 374w |
/hosting |
200 | 171 | 54 | 148 | 1 | 383 | 2/2 | 1 | 383w |
/ai-automation |
200 | 252 | 80 | 158 | 1 | 1118 | 2/2 | 1 | title 80 |
/service-industry |
200 | 600 | 75 | 184 | 1 | 721 | 2/2 | 1 | title 75 desc 184 |
/restaurants |
200 | 251 | 96 | 200 | 1 | 659 | 2/2 | 1 | title 96 desc 200 |
/restaurants/ordering |
200 | 198 | 86 | 185 | 1 | 401 | 2/2 | 1 | title 86 desc 185 no og:image |
/restaurants/pos |
200 | 216 | 84 | 183 | 1 | 363 | 2/2 | 1 | title 84 desc 183 no og:image 363w |
/restaurants/signage |
200 | 193 | 54 | 148 | 1 | 409 | 2/2 | 1 | clean |
/restaurants/seo |
200 | 205 | 54 | 148 | 1 | 408 | 2/2 | 1 | clean |
/restaurants/printing |
200 | 190 | 54 | 148 | 1 | 407 | 2/2 | 1 | clean |
/restaurants/photography |
200 | 554 | 54 | 148 | 1 | 480 | 2/2 | 1 | clean |
/restaurants/online-ordering-costs |
200 | 215 | 83 | 200 | 1 | 1454 | 2/2 | 2 | title 83 desc 200 |
/boom-online-ordering |
200 | 235 | 75 | 251 | 1 | 2083 | 2/2 | 1 | title 75 desc 251 no og:image |
/gloriafood-replacement |
200 | 199 | 91 | 192 | 1 | 823 | 2/2 | 3 | title 91 desc 192 no og:image |
/gloriafood-migration-guide |
200 | 245 | 68 | 133 | 1 | 868 | 2/2 | 1 | title 68 |
/bar |
200 | 204 | 55 | 185 | 1 | 993 | 2/2 | 1 | desc 185 |
/compliee |
200 | 119 | 84 | 156 | 1 | 656 | 2/2 | 1 | title 84 |
/investment |
200 | 259 | 60 | 183 | 1 | 2602 | 2/2 | 1 | desc 183 |
/seo-audit |
200 | 186 | 51 | 154 | 1 | 233 | 2/2 | 1 | 233w |
/blog |
200 | 120 | 37 | 134 | 1 | 358 | 2/2 | 1 | no og:image 358w |
/blog/gloriafood-alternatives |
200 | 203 | 71 | 159 | 1 | 1925 | 2/2 | 5 | title 71 |
/blog/boom-vs-competitors |
200 | 240 | 69 | 193 | 1 | 517 | 2/2 | 2 | title 69 desc 193 |
/privacy |
200 | 219 | 54 | 148 | 1 | 970 | 2/2 | 1 | clean |
/terms |
200 | 206 | 54 | 148 | 1 | 1166 | 2/2 | 1 | clean |
@type.
ComparisonTable node and re-test in the Rich Results tool./services/* pages, and add the 5 missing og:images./websites, /hosting and /restaurants/pos.robots.ts + next.config.ts, commit 922f09c).