DEV Community

Cover image for Fixing SEO for a React SPA Marketplace on Firebase Hosting
Louis Viney
Louis Viney

Posted on

Fixing SEO for a React SPA Marketplace on Firebase Hosting

I recently ran into an SEO problem while building a marketplace for Ionic templates and UI kits.

The stack looked reasonable at first:

  • React 18
  • React Router 7
  • Vite
  • Firebase Hosting
  • Firestore
  • react-snap for prerendering

The site worked perfectly for users but Google hated it and none of the pages were indexing.

After digging into Search Console, page source output, and prerender behaviour, I discovered several compounding problems that are easy to miss when building modern SPA marketplaces.

This post breaks down:

  • what was failing
  • why Google wasn’t indexing the site
  • and the architecture I ended up using to fix it

The Initial Problem

The homepage source initially looked something like this:

<body>
  <div id="root"></div>
</body>
Enter fullscreen mode Exit fullscreen mode

The visible content existed only after hydration. Google can render JavaScript, but relying entirely on client-side rendering creates several problems:

  • slower indexing
  • inconsistent crawling
  • weak internal-link discovery
  • poor crawl prioritisation on new domains

This became especially painful for marketplace product pages. Google was discovering URLs but refusing to index them.

Search Console showed:

  • "Discovered – currently not indexed"
  • "Crawled – currently not indexed"

So that meant that the pages were technically reachable but Google didn’t think they were strong enough to index yet


The First Attempt: react-snap

I added react-snap to prerender the site. At first this seemed to work.
The homepage source now contained:

  • headings
  • intro copy
  • category links

But product pages still weren’t indexing. After deeper inspection, I found that the product pages were never actually being prerendered.

react-snap had:

crawl: false

and no dynamic route list for:

/template/:id

So every product page URL returned the generic SPA shell with homepage metadata.

Even worse, the product pages fetched data from Firestore but
react-snap blocks third-party requests during prerender so product data never loaded during snapshot generation.

The result was:

  • homepage title on every product page
  • homepage meta description on every product page
  • no product-specific canonical tags
  • no product-specific OpenGraph tags
  • weak semantic HTML

From Google’s perspective, these pages looked like low-value duplicates.


Constraints of Firebase Hosting

Firebase Hosting is static-only, that meant no runtime SSR, no server-side route generation and no server-rendered metadata.

To achieve static hosting, fast global delivery with low infrastructure complexity, I leaned fully into build-time generation because migrating to a Node SSR architecture felt excessive.


The Architecture That Worked

The fix was to euery Firestore for all published products before every build and export them into a local JSON file public/ssg-data.json. I then regenerated the sitemap.xml and prerender the route list to create a local source of truth available during prerendering.

Now react-snap blocks Firestore requests but it does NOT block local file requests. Now during prerender, the pages fetched /ssg-data.json which made the product data available allowing SEO metadata to be rendered statically and product links to exist in initial HTML.

The next critical fix was generating dynamic routes automatically during prebuild:

const routes = products.map(
  p => `/template/${p.id}`
)
Enter fullscreen mode Exit fullscreen mode

These routes are injected into the react-snap include list and every published product page gets snapshotted during build. No more SPA shell for product URLs.


Product-Specific SEO Metadata

Once product data became available during prerender, every product page could generate:

  • unique
  • unique meta description
  • canonical URL
  • OpenGraph tags
  • Twitter tags
  • Product JSON-LD
  • Breadcrumb JSON-LD

For example:

<title>
NovaClip - Short-Form Video App — Ionic React UI Kits
</title>
Enter fullscreen mode Exit fullscreen mode

instead of:

<title>
Ionic Market — Premium Ionic Templates...
</title>
Enter fullscreen mode Exit fullscreen mode

Final issue was the homepage and category pages initially did not contain direct product links in source HTML. Because Google relies heavily on <a href="/template/..."> for discovery and crawl prioritisation, I added featured products on homepage, category product grids and related products on product pages.

The build flow now looks like this:

npm run build
  → prebuild
      query Firestore
      export ssg-data.json
      regenerate sitemap.xml
      generate prerender routes
  → vite build
  → react-snap prerender
      pages fetch local SSG data
      product pages render full metadata
  → firebase deploy
Enter fullscreen mode Exit fullscreen mode

The important detail is that the prerender step no longer depends on Firestore.


Results

After these fixes, the product pages now contain:

  • real semantic HTML
  • unique metadata
  • canonical tags
  • structured data
  • related links
  • product-specific OpenGraph tags

And the sitemap now contains:

  • canonical product URLs
  • canonical category URLs
  • no query-string filter pages

Most importantly, the product pages are now actual HTML documents and
not generic SPA fallback pages.


Final Thoughts

I still like the React/Vite/Firebase Hosting stack but if you’re building a marketplace or SEO-heavy application, you need to think carefully about all these:

  • prerender coverage
  • metadata generation timing
  • internal linking
  • dynamic route discovery
  • build-time data availability

Otherwise Google may crawl your pages while still seeing a generic application shell and that can quietly kill indexing. For anyone curious, the implementation is currently powering https://ionicmarket.com which is a marketplace for Ionic templates, UI kits, feature modules, and Capacitor plugins.

Top comments (0)