syted

syted blog

SEO for SaaS: What a Founder Can Ship Without an Agency

Read the guides that rank for this query and you get the same five stages every time: technical foundation, keyword research, content creation, link building, monitoring. The advice is not wrong. It is written for someone whose job title is "content marketer" and whose Monday consists of briefs and a CMS.

That is not you. You have a repo, a deploy pipeline, maybe two hours on a Thursday, and a product that changes faster than any content calendar can track. The useful question is not "what is a SaaS SEO strategy." It is "what can I ship this week that will still matter in six months, and what should I refuse to do."

This article answers in that order. Every section has something you can run against your own domain today, and a reason to run it. Where the honest answer is "this does not pay for the time," it says so.

What makes SaaS SEO different from SEO in general

Three structural facts about a SaaS product change the work, and they are the only three that matter.

Your app is invisible to search. Most of your code lives behind a login. Whatever Google indexes is a thin public shell around a large private application. An ecommerce site has ten thousand product pages that exist to be indexed. You have maybe fifteen public URLs that exist to be read by humans who are already interested, plus whatever you publish deliberately. The ratio of "pages that could rank" to "engineering effort spent" is brutally low unless you create pages on purpose.

Your buyer searches for the problem, not for you. Nobody types your product name until after they have found you some other way. They type the shape of their problem, often as a question, often with a competitor's name in it. That means the keyword that converts is rarely the keyword with the volume, and the gap between the two is where most SaaS content budgets die.

Your changes ship continuously. A marketing site that gets rebuilt twice a year can afford a static sitemap and a manual metadata pass. Yours cannot. Anything you do by hand will drift out of sync within a month. The parts of SEO worth your time are the parts you can express as code that runs on every deploy.

Everything below follows from those three.

Before anything else: can a crawler read your app?

This is first because it is the only step where failure is total. If the HTML your server returns is an empty div, nothing else in this article can help you, and you will spend months wondering why good content is not ranking.

Check it the way a crawler sees it, which means no browser and no JavaScript:

curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://yourdomain.com/pricing | grep -c "<h1"

If that returns 0, your page has no server-rendered heading. Look at the actual body:

curl -sL https://yourdomain.com/pricing | head -c 2000

You are looking for your real copy in the response. Not a loading skeleton, not <div id="root"></div>, not a <script> tag carrying a JSON blob that becomes the page once React boots. Google will usually render JavaScript eventually, but "eventually" is a queue you do not control, and the retrieval systems behind AI assistants are far less patient. We went through the crawler-by-crawler detail of that in the piece on what actually gets you cited by ChatGPT, and the short version is that server-rendered HTML is the cheapest insurance in this entire field.

In the App Router, a page is a Server Component by default and renders on the server unless you opt out. The common way founders break this is marking a whole route 'use client' because one button inside it needs useState. Move the interactivity down instead:

// app/pricing/page.tsx  (stays a Server Component)
import { PlanToggle } from './plan-toggle'

export default function PricingPage() {
    return (
        <main>
            <h1>Pricing</h1>
            <p>One plan. Everything included.</p>
            <PlanToggle />
        </main>
    )
}
// app/pricing/plan-toggle.tsx
'use client'
import { useState } from 'react'

export function PlanToggle() {
    const [annual, setAnnual] = useState(false)
    return <button onClick={() => setAnnual(!annual)}>{annual ? 'Annual' : 'Monthly'}</button>
}

The heading and the copy are now in the HTML. The button still works.

Second, check that you are not blocking yourself. Fetch your own robots file and read it as an adversary would:

curl -s https://yourdomain.com/robots.txt
curl -sI https://yourdomain.com/pricing | grep -i "x-robots-tag"

A Disallow: / left over from a staging environment, or an X-Robots-Tag: noindex header set at the edge for a preview deployment and never scoped properly, will quietly delete your site from search. This happens more often than anyone admits, and it costs nothing to check monthly.

Third, generate the sitemap from your data, not from a list you maintain:

// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/blog'

const SITE_URL = 'https://yourdomain.com'

export default function sitemap(): MetadataRoute.Sitemap {
    const posts = getAllPosts()
    return [
        { url: SITE_URL, lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
        { url: `${SITE_URL}/docs`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
        ...posts.map((post) => ({
            url: `${SITE_URL}/blog/${post.slug}`,
            // The publish date, not the build date. An unchanged article that
            // claims to be modified on every deploy makes the signal worthless.
            lastModified: new Date(post.publishedAt),
            changeFrequency: 'monthly' as const,
            priority: 0.7,
        })),
    ]
}

That lastModified comment is the part people get wrong. It is tempting to use new Date() everywhere because it compiles. Doing so tells Google that every page on your site changed at 3am last night, every night, which teaches it to ignore the field entirely.

Leave authenticated routes out of the sitemap and disallow them in robots. They return redirects to a crawler, and pointing at them burns crawl budget for no return.

The page types that carry a SaaS site, and what each one has to do

A SaaS site has four kinds of public page. They fail in different ways and deserve different amounts of your attention.

Page type Search job Typical failure Effort worth spending
Homepage Rank for your brand, explain the category Written for investors, not for searchers Low, once
Product and feature pages Catch problem-shaped queries near the buy decision One page trying to cover six problems High
Docs Catch integration and error-message queries Blocked from indexing, or on a subdomain Medium, high leverage
Blog and guides Catch everything upstream of the decision Published, then never touched again Ongoing

Docs are the underrated one. Developers search error strings and library names, your docs are the only page on the internet that contains yours, and the intent is about as qualified as intent gets. If your docs sit behind a JavaScript-only renderer, or on docs.yourdomain.com rather than yourdomain.com/docs, you have handed away the easiest traffic you will ever get. Subdomain versus subfolder is a real argument with real nuance, but for a site with no domain authority to spare, consolidating on one hostname is the safer default.

For metadata, write it once as a function of your data rather than page by page. In the App Router that is generateMetadata, and note that params is a Promise you have to await:

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { getPost } from '@/lib/blog'

type Props = { params: Promise<{ slug: string }> }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
    const { slug } = await params
    const post = await getPost(slug)

    return {
        title: post.title,
        description: post.description,
        alternates: { canonical: `/blog/${slug}` },
        openGraph: {
            title: post.title,
            description: post.description,
            type: 'article',
            publishedTime: post.publishedAt,
        },
    }
}

The relative canonical only resolves if metadataBase is set in your root layout. Without it, a relative URL in a metadata field is a build error, so you will find out immediately rather than shipping localhost URLs to production:

// app/layout.tsx
export const metadata: Metadata = {
    metadataBase: new URL('https://yourdomain.com'),
    title: { template: '%s | Acme', default: 'Acme' },
}

Canonicals matter more for a SaaS than people expect, because marketing sends traffic to /?utm_source=...&utm_campaign=... and each of those is a distinct URL to a crawler. A self-referencing canonical on every page collapses them back to one.

Picking keywords: the audience check comes before the volume

This is where most of the wasted effort in SaaS content lives, and it is the step you can do best precisely because you are close to the product.

The usual process is: pull a keyword tool, sort by volume, filter by difficulty, write the top ten. The result is technically defensible and commercially useless, because volume and difficulty say nothing about who is searching.

Run this check instead, and run it before you write a single word. Open the actual search results for the keyword. Look at the top five pages and ask one question: who is each of these written for?

Take seo for saas, the phrase this article targets. The results, checked before writing, look roughly like this: an AI Overview at the top, then a guide written by a solo marketer, then a Reddit thread in r/SaaS from a founder asking where to even start, then a business-productivity blog, and from position six onward a run of agency service pages and agency blogs.

Two conclusions follow. First, the top of the page is founders and independent writers, which is the reader this article wants, so the keyword is legitimate. Second, the agency layer starting at position six tells you exactly what the page is missing: not one of those results contains a command you can run or a line of code you can paste. That gap is the entire reason to write.

Now run the same check on a keyword that looks better on paper. saas seo agency has roughly 3,600 monthly searches at a difficulty of 5. Every result is a firm selling a retainer, because the intent of the query is hire someone. If you sell a self-serve product, ranking there produces traffic and no signups. That is not a small inefficiency, it is the entire failure mode, and volume is the number that hides it.

Two mechanical habits make this cheap:

Read the parent topic, not just the keyword. Most keyword tools will tell you which broader topic a query rolls up into. A dozen phrasings usually share one parent, which means one article covers all of them. Writing a separate post per phrasing gives you a set of near-duplicate pages competing against each other, and Google resolves that by indexing none of them well. When Search Console starts reporting pages as "crawled, currently not indexed", substantial overlap with your own other pages is one of the common causes, and it is entirely self-inflicted.

Set a difficulty ceiling and hold it. On a site with no backlink profile, a keyword whose first page is owned by established publishers is not a target, it is a hobby. Pick a ceiling, apply it without negotiating with yourself, and revisit the excluded list when your domain has actually earned authority. Write the rejected keywords down with the reason. Otherwise you will rediscover the same attractive dead end every quarter.

Writing pages that survive contact with a real reader

Once the keyword is chosen, the writing job has a narrow definition: answer the question the searcher asked, more completely than the pages currently answering it, in a form a machine can quote.

A few things that hold up in practice.

Answer in the first sentence under the heading. Not a preamble, not a restatement of the question. If the H2 asks how to check whether a crawler can read your app, the next sentence should start doing that. This is good for readers who scan, and it is what makes a passage liftable by a retrieval system that is looking for a self-contained answer.

Write passages that stand alone. An AI assistant pulls a paragraph out of your page and drops it into an answer with no surrounding context. If your paragraph starts with "As mentioned above, this is why it matters," it is unusable. Each section should make sense cold.

Show the command, not a description of the command. A reader who can paste curl -sI https://yourdomain.com | grep -i x-robots-tag and see their own header learns something in four seconds. A paragraph explaining that you should check your response headers teaches nothing and is indistinguishable from every other article on the subject.

Give real numbers, and cite where they come from. "The URL Inspection API allows 2,000 queries per day per site" is checkable. "The API has generous limits" is filler. When you cannot verify a number, write the range or say what date you checked, rather than inventing precision.

Add JSON-LD where it describes something real. For articles, BlogPosting is well supported and cheap to emit from data you already have:

export default async function ArticlePage({ params }: Props) {
    const { slug } = await params
    const post = await getPost(slug)

    const jsonLd = {
        '@context': 'https://schema.org',
        '@type': 'BlogPosting',
        headline: post.title,
        description: post.description,
        datePublished: post.publishedAt,
        dateModified: post.updatedAt ?? post.publishedAt,
        author: { '@type': 'Organization', name: 'Acme' },
        mainEntityOfPage: `https://yourdomain.com/blog/${slug}`,
    }

    return (
        <article>
            <script
                type="application/ld+json"
                dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
            />
            {/* ... */}
        </article>
    )
}

Do not extend this into invented review or rating markup for things nobody reviewed. That is a manual action waiting to happen, and it buys nothing.

One thing to be clear about, because it comes up constantly: Google's own documentation on AI features states that you do not need to create new machine readable files, "AI text files", or markup to appear in AI Overviews or AI Mode, and that there is no special schema.org structured data required. Anyone selling you a file format as the key to AI visibility is selling you something Google says is not necessary.

On AI-written drafts. Google's spam policies do not prohibit AI-generated content. What they prohibit is scaled content abuse, defined as generating many pages "for the primary purpose of manipulating search rankings and not helping users", explicitly including using generative tools to produce many pages without adding value. The policy is about the value of the output, not the tooling. In practice this means a generated draft that you verify, correct and would defend to a customer is fine, and two hundred spun variations of the same page are not, whoever or whatever wrote them.

Measuring without a dashboard: the Search Console API in 40 lines

You do not need an SEO platform to know what is happening. Search Console has an API, your data is in it, and pulling it yourself takes less time than evaluating a tool.

The Search Analytics endpoint gives you queries, pages, clicks, impressions and average position:

curl -s -X POST \
  "https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fyourdomain.com%2F/searchAnalytics/query" \
  -H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startDate": "2026-07-01",
    "endDate": "2026-07-31",
    "dimensions": ["query", "page"],
    "rowLimit": 500
  }' | jq -r '.rows[] | [.keys[0], .keys[1], .clicks, .impressions, (.position|round)] | @tsv'

Note the double URL encoding of the property name in the path. That is the single most common reason this call returns a 403 on the first try.

The number worth watching is not total clicks. It is the set of queries with high impressions and a click-through rate near zero. Each of those is a page Google is already showing to people who then choose something else, which is a title and description problem on a page that already exists, and it is the cheapest fix available to you. Rewriting one title takes ten minutes and needs no new content.

The second endpoint worth knowing is URL Inspection, which tells you what Google actually thinks of a specific URL:

curl -s -X POST "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect" \
  -H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inspectionUrl": "https://yourdomain.com/blog/some-post",
    "siteUrl": "https://yourdomain.com/"
  }' | jq '.inspectionResult.indexStatusResult | {verdict, coverageState, robotsTxtState, lastCrawlTime}'

coverageState is the field that answers "why is this page not showing up." Google documents the quotas plainly: URL Inspection allows 2,000 queries per day and 600 per minute per site, and Search Analytics allows 1,200 queries per minute per site. Both are far above anything a weekly script needs, so wire it into cron and stop guessing.

A note on measuring AI traffic, since it is the question everyone asks next: Google states that sites appearing in AI features are included in overall Search Console traffic, reported under the "Web" search type, and not broken out separately. You cannot isolate AI Overview clicks in Search Console today. Anyone offering you that number from Search Console data is estimating.

What is not worth your time

Saying what to skip is more useful than adding another item to a checklist, so here is the honest list for a founder with limited hours.

Chasing Core Web Vitals below the threshold. Getting from a failing score to a passing one is worth doing. Grinding a passing score from 88 to 96 is engineering theatre. Fix the layout shift caused by your web font, fix the hero image that loads at 2MB, then stop.

Buying links, or "digital PR" as a first move. Links matter. Paid link schemes are a spam policy violation, and a founder without a content base to link to has nothing to earn links with anyway. Build something worth citing first.

Publishing on a cadence you cannot hold. Four articles a month, forever, beats twenty in January and none after. The single strongest predictor of whether a blog works is whether it is still being updated in month nine.

Keyword density, meta keywords, and LSI. The meta keywords tag has been ignored by Google for well over a decade. Nothing in this category has been true for a long time, and every hour spent there is an hour not spent on the crawlability check at the top of this article.

Rewriting your homepage for a head term. Your homepage should convert the people who already know who you are. Trying to make it rank for a broad category term usually damages it for both jobs.

Waiting for results on a schedule. Indexing a new page can take days or weeks, and building the authority to compete for a contested term takes considerably longer. No one can guarantee a position in Google, and anybody who does is describing something they do not control. What you can control is whether the page is crawlable, whether it answers the question better than what currently ranks, and whether you are still publishing in six months.

The compounding part of this is real, though. A technically clean site that publishes one genuinely useful page a week for a year has 52 assets that keep working. The same effort spent on posts nobody reads produces a folder of files. The difference between the two is entirely decided at the keyword step, before any writing happens, which is why that section is the longest one here.

FAQ

How do you do SEO for a SaaS product?

In this order: confirm a crawler can read your pages without JavaScript, generate your sitemap and metadata from your data so they cannot drift, choose keywords by checking who currently ranks rather than by volume, write pages that answer the question in the first sentence, and measure with the Search Console API. The technical work is a few days of engineering done once. The keyword and writing work is ongoing, and it is where the outcome is decided.

Why is technical SEO important for SaaS startups?

Because a SaaS site is usually a JavaScript application, and the failure modes are silent. A marketing site built in a CMS ships readable HTML by default. Yours can ship an empty div, a stray noindex header from a preview environment, or a sitemap that lies about modification dates, and none of that produces an error anyone notices. The technical layer does not win you rankings on its own, but any failure in it caps everything else at zero.

Is SEO still worth it now that AI Overviews answer the query?

It changed shape rather than ending. Some informational queries now get answered on the results page without a click, which reduces traffic to pages whose only job was to define a term. Queries with a decision behind them still send people to sites. Separately, the same pages that rank are largely the pages that get quoted by assistants, so the work overlaps heavily with getting cited by AI assistants. The honest position is that measuring the split is currently hard, because Google folds AI feature traffic into overall Search Console numbers rather than reporting it separately.

What is the best SEO strategy for a SaaS company?

The one you can execute with the people you have. For a two-person team, that is usually: fix crawlability once, publish one well-researched page a week aimed at problem-shaped queries, link those pages to each other and to your docs, and review Search Console monthly for existing pages with impressions and no clicks. Strategies that require a content team, a link building budget and a monthly reporting cycle are not better, they are just written for a company that is not yours.

Should I hire an SEO agency or do it myself?

Do the technical layer yourself, always. It is a few days of work in your own codebase and nobody outside your team can do it faster. The parts genuinely worth outsourcing are keyword research and consistent publishing, because both are time-consuming and neither improves by being done by the person who built the product. Full disclosure on our side: syted is a product in that second category, $99 per month for 30 articles published on your own domain, so treat this answer as coming from an interested party and judge any option on whether it runs a real audience check before writing.

How long does SEO take to work for a SaaS?

Long enough that you should not plan around a date. A new page on an established site can be indexed within days; a new domain competing for a contested term can take many months, and results vary enormously with how much competition the keyword actually has. The useful framing is leading indicators rather than a deadline: is the page indexed, is it collecting impressions, is its average position moving in the right direction. If those three are true, the work is functioning. If a page has been indexed for three months with no impressions at all, the keyword choice was wrong and no amount of patience will fix it.

Get cited by ChatGPT. Rank on Google.

You found this article through search. That is the whole product.

  • One researched article a day
  • Published on your own domain
  • Keywords checked against live results
Start writing

Get cited by ChatGPT. Rank on Google.

You found this article through search. That is the whole product.

  • One researched article a day
  • Published on your own domain
  • Keywords checked against live results
Start writing