SEO Optimization Guide

Overview

Getting your SPA to rank well in search engines takes more than just pre-rendering. You also need the right meta tags, structured data, and semantic HTML in place so that search engines understand what your pages are about.

This guide covers the core SEO fundamentals you should implement on every page. If you are using a framework like Lovable, Bolt, or any React-based SPA, these techniques apply directly to your codebase.

Meta Tags

Title Tag

The <title> tag is the single most important on-page SEO element. It appears in search results, browser tabs, and social shares.

Best practices:

  • Keep titles between 50-60 characters (Google truncates at ~60)
  • Put your primary keyword near the beginning
  • Make every page title unique
  • Use a consistent format like Page Name | Brand
ExampleVerdict
Affordable Gutter Installation in St. George, UT | CustomGuttersGood -- keyword-first, location, brand, 58 chars
HomeBad -- no keywords, no context, wastes the title tag
Welcome to Our Amazing Website - The Best Website Ever Created For All Your NeedsBad -- too long, keyword-stuffed, gets truncated
Gutter Repair Services | St. George & Hurricane, UTGood -- clear service, location, 52 chars

Meta Description

The meta description does not directly affect rankings, but it controls the snippet shown in search results. A compelling description improves click-through rates.

Best practices:

  • Keep descriptions between 150-160 characters
  • Include your primary keyword naturally
  • Write a clear call to action or value proposition
  • Make every page description unique
ExampleVerdict
Professional gutter installation and repair in St. George, UT. Free estimates, 5-star reviews. Call today for a quote.Good -- service, location, CTA, 116 chars
We are a company that does things.Bad -- vague, no keywords, no value
Click here to learn more about our services and offerings that we provide to customers in the area.Bad -- generic, no specifics

Implementation with React Helmet

import { Helmet } from "react-helmet-async";

function AboutPage() {
  return (
    <>
      <Helmet>
        <title>About Us | Your Company Name</title>
        <meta
          name="description"
          content="Learn about Your Company — a team of web developers in St. George, UT helping local businesses grow online since 2020."
        />
      </Helmet>
      <main>{/* page content */}</main>
    </>
  );
}

Open Graph Tags

Open Graph (OG) tags control how your pages look when shared on Facebook, LinkedIn, Discord, and most other platforms. Without them, social platforms will guess — and they usually guess wrong.

The 5 Essential Tags

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A compelling description of this page." />
<meta property="og:image" content="https://www.example.com/images/og-banner.jpg" />
<meta property="og:url" content="https://www.example.com/page" />
<meta property="og:type" content="website" />

Implementation

<Helmet>
  <meta property="og:title" content="About Us | Your Company" />
  <meta property="og:description" content="We build websites that actually rank." />
  <meta property="og:image" content="https://www.example.com/images/about-og.jpg" />
  <meta property="og:url" content="https://www.example.com/about" />
  <meta property="og:type" content="website" />
</Helmet>

Important: The og:image URL must be absolute (starting with https://). Relative paths like /images/og.jpg will not work. The recommended image size is 1200x630 pixels.

For a deeper dive on social previews and platform-specific requirements, see the Open Graph & Social Previews guide.

Twitter Card Tags

Twitter (X) uses its own set of meta tags for link previews. If Twitter Card tags are missing, Twitter falls back to Open Graph tags — but you get better control by setting both.

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Your Page Title" />
<meta name="twitter:description" content="A compelling description of this page." />
<meta name="twitter:image" content="https://www.example.com/images/twitter-card.jpg" />

The summary_large_image card type shows a large banner image above the title and description. This is what you want for most pages.

<Helmet>
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="About Us | Your Company" />
  <meta name="twitter:description" content="We build websites that actually rank." />
  <meta name="twitter:image" content="https://www.example.com/images/about-og.jpg" />
</Helmet>

JSON-LD Structured Data

Structured data tells search engines what your content actually is — not just what it says. This is what enables rich results like star ratings, FAQ dropdowns, and business info cards in Google.

JSON-LD is the recommended format. You add it as a <script> tag in your page's <head>.

Organization

Use this on your homepage to describe your business:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Your Company Name",
  "url": "https://www.example.com",
  "logo": "https://www.example.com/logo.png",
  "sameAs": [
    "https://www.facebook.com/yourcompany",
    "https://www.instagram.com/yourcompany"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "telephone": "+1-435-555-0100",
    "contactType": "customer service"
  }
}
</script>

LocalBusiness

If you serve a specific area, use LocalBusiness instead of (or in addition to) Organization:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Custom Gutters St. George",
  "image": "https://www.example.com/storefront.jpg",
  "url": "https://www.example.com",
  "telephone": "+1-435-555-0100",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "123 Main Street",
    "addressLocality": "St. George",
    "addressRegion": "UT",
    "postalCode": "84770",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 37.0965,
    "longitude": -113.5684
  },
  "openingHoursSpecification": {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
    "opens": "08:00",
    "closes": "17:00"
  }
}
</script>

Article

Use this for blog posts and content pages:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "How to Improve Your SPA's SEO",
  "author": {
    "@type": "Person",
    "name": "Your Name"
  },
  "datePublished": "2026-01-15",
  "dateModified": "2026-02-01",
  "image": "https://www.example.com/images/seo-article.jpg",
  "publisher": {
    "@type": "Organization",
    "name": "Your Company",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.example.com/logo.png"
    }
  }
}
</script>

Product

Use this for e-commerce product pages:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Premium Gutter Guard",
  "image": "https://www.example.com/products/gutter-guard.jpg",
  "description": "Heavy-duty aluminum gutter guard that keeps leaves and debris out.",
  "brand": {
    "@type": "Brand",
    "name": "CustomGutters"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/products/gutter-guard",
    "priceCurrency": "USD",
    "price": "29.99",
    "availability": "https://schema.org/InStock"
  }
}
</script>

Adding JSON-LD in React

function HomePage() {
  const structuredData = {
    "@context": "https://schema.org",
    "@type": "Organization",
    name: "Your Company",
    url: "https://www.example.com",
    logo: "https://www.example.com/logo.png",
  };

  return (
    <>
      <Helmet>
        <script type="application/ld+json">
          {JSON.stringify(structuredData)}
        </script>
      </Helmet>
      <main>{/* page content */}</main>
    </>
  );
}

Semantic HTML

Search engines rely on HTML structure to understand your content hierarchy. Good semantic HTML makes your content easier to parse and can improve rankings.

Heading Hierarchy

Use a single <h1> per page for the main topic, then <h2> for sections and <h3> for subsections:

<h1>Gutter Installation Services</h1>           <!-- one per page -->
  <h2>Residential Gutters</h2>                   <!-- main sections -->
    <h3>Seamless Aluminum Gutters</h3>           <!-- subsections -->
    <h3>Copper Gutters</h3>
  <h2>Commercial Gutters</h2>
    <h3>Industrial Rain Collection</h3>

Do not skip levels (e.g., jumping from <h1> to <h3>) and do not use headings just for visual styling. Use CSS for that.

Alt Text on Images

Every meaningful image should have an alt attribute that describes what the image shows:

<!-- Good -->
<img src="/gutters.jpg" alt="Seamless aluminum gutters installed on a two-story home in St. George, Utah" />

<!-- Bad -->
<img src="/gutters.jpg" alt="image" />
<img src="/gutters.jpg" />

Decorative images (dividers, backgrounds) can use an empty alt="" to signal that they carry no meaning.

Semantic Elements

Use semantic HTML5 elements instead of generic <div> wrappers when possible:

<header>  <!-- site header / navigation -->
<nav>     <!-- navigation links -->
<main>    <!-- primary page content -->
<article> <!-- self-contained content like blog posts -->
<section> <!-- thematic grouping of content -->
<aside>   <!-- sidebar or tangential content -->
<footer>  <!-- site footer -->

AI Prompts for Lovable and Bolt Users

If you are building your site with Lovable, Bolt, or another AI-powered builder, you can paste these prompts directly to implement the SEO techniques from this guide.

Add SEO Component

Add react-helmet-async and create a reusable SEO component with title, description,
and og:image props. Use it on every page with unique values. The component should set
the title tag, meta description, og:title, og:description, og:image, og:url, og:type,
twitter:card, twitter:title, twitter:description, and twitter:image.

Create a Sitemap

Create a sitemap.xml file in the /public folder that lists all of my routes.
Use the standard XML sitemap format with loc, lastmod, changefreq, and priority
for each URL. Set the homepage priority to 1.0 and other pages to 0.8.

Add Structured Data

Add JSON-LD structured data to my homepage using a script tag in the head.
Use the LocalBusiness schema type with my business name, address, phone number,
opening hours, and geo coordinates. Also add Organization schema with my logo
and social media links.

Add a Robots.txt

Create a robots.txt file in the /public folder. Allow all crawlers to access
all pages. Add a Sitemap directive pointing to https://mydomain.com/sitemap.xml.

After Making Changes

Once you have implemented SEO improvements, purge your cache from the RndrKit dashboard so that search engine bots see your updates immediately. Go to your domain's Cache tab, select the pages you updated, and click Purge.

Without purging, bots may continue to receive the old cached HTML until the cache expires naturally (typically 1 hour).

Next Steps