DEV Community

Serhii Kalyna
Serhii Kalyna

Posted on

How I fixed Google ignoring 160 pages: migrating from Vite SPA to Next.js SSG

How I fixed Google ignoring 160 pages: migrating from Vite SPA to Next.js SSG

I've been building Convertify — a free image converter
that supports HEIC, WebP, AVIF, PNG, JPG and 20+ formats.

The problem: after weeks of work, Google had indexed exactly 2 pages out of 160+.

Why Google ignored my pages

My app was a Vite React SPA. Every route looked like this in HTML:

<!DOCTYPE html>
<html>
  <head>
    <title>Convertify</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/assets/index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Google crawls without executing JavaScript. So every single page —
/heic-to-jpg, /webp-to-png, /avif-to-jpg — returned identical empty HTML.
Google saw them all as duplicates and indexed only the homepage.

The fix: Next.js App Router + generateStaticParams

I migrated to Next.js and used generateStaticParams() to pre-render
all format pairs at build time:

export async function generateStaticParams() {
  const formats = ['heic', 'jpg', 'jpeg', 'png', 'webp', 'avif', 'gif', 'bmp', 'tiff']
  const params = []
  for (const from of formats) {
    for (const to of formats) {
      if (from !== to) params.push({ slug: `${from}-to-${to}` })
    }
  }
  return params
}
Enter fullscreen mode Exit fullscreen mode

Result: 186 static HTML pages generated at build time.

Server components for SEO content

The key insight: not everything needs to be a client component.

I split the page into two parts:

Client (interactive): DropZone, Settings, ConvertButton, FilesViewer
Server (static HTML): FAQ, HowToConvert, SupportedFormats, Header, Footer

For server components I replaced MUI Accordion with native HTML <details>/<summary>:

// No 'use client', no JavaScript, pure HTML
const FAQ = ({ from, to }: { from?: string; to?: string }) => {
  return (
    <dl>
      {faqs.map((faq, i) => (
        <details key={i}>
          <summary>{faq.question}</summary>
          <dd>{faq.answer}</dd>
        </details>
      ))}
    </dl>
  )
}
Enter fullscreen mode Exit fullscreen mode

Google gets full FAQ content without running any JavaScript.

Dynamic metadata per page

Instead of react-helmet-async, Next.js handles metadata server-side:

export async function generateMetadata({ params }) {
  const [from, to] = params.slug.split('-to-')
  return {
    title: `Convert ${from.toUpperCase()} to ${to.toUpperCase()} Online Free — Convertify`,
    description: `Free online ${from.toUpperCase()} to ${to.toUpperCase()} converter.`,
    alternates: { canonical: `https://convertifyapp.net/${params.slug}` },
  }
}
Enter fullscreen mode Exit fullscreen mode

Every page now has unique title, description and canonical tag in the HTML
— no JavaScript required.

Results

  • Before: 2 indexed pages
  • After: 186 static pages pre-rendered
  • PageSpeed Desktop: 100/100 Performance, 100/100 SEO
  • PageSpeed Mobile: 83/100 Performance, 100/100 SEO

The mobile performance hit comes from MUI (emotion) on the client side.
Working on reducing that next.

Lessons learned

  1. SPAs are SEO poison for content pages. Use SSG or SSR.
  2. Split server/client components — only interactive parts need JavaScript.
  3. Native HTML elements (<details>, <summary>) work great for accordions and are fully server-renderable.
  4. generateStaticParams is incredibly powerful for programmatic SEO pages.

If you're building a tool with many landing pages — don't make the same
mistake I did. Start with Next.js from day one.


Check out Convertify if you need to convert
HEIC, WebP, AVIF or any other image format — free, no signup, no limits.

Top comments (0)