React components
Render every block type with components you own, restyle any of them, and never break on a new one.
Articles arrive as typed blocks, so each part of an article is a component you control: restyle the callout, swap the call to action for your signup form, hand images to your framework's image component. The set below renders every block type with semantic HTML and one stylesheet.
How Google decides what to show for a search
Quick answer: Google finds pages by crawling, stores what it understands in an index, and ranks the indexed pages for each query by how well they answer it. Most of what you can influence happens in the last step.
Crawling, indexing, ranking
Google describes the process in three stages in its own guide to how Search works. A page that is never crawled cannot be indexed, and a page that is not indexed cannot rank.
- Crawling: discovering URLs and downloading pages.
- Indexing: reading a page and storing what it is about.
- Serving: choosing and ordering results for one query.

What a crawler needs
- A link to the page from somewhere it already knows.
- A
sitemap.xmllisting the URL. - A server that answers with
200.
Signals that decide the order
| Signal | What it measures | What you control |
|---|---|---|
| Relevance | Does the page answer this query? | Topic and structure |
| Quality | Is it trustworthy and original? | Sources and depth |
| Usability | Does it load and read well? | Your templates |
Our goal is to organize the world’s information and make it universally accessible and useful.
<meta name="robots" content="noindex">
FAQ
How long does it take for a new page to appear in Google?
Anywhere from a few days to a few weeks. On our own sites the median is three days to the first impression.
Does submitting a sitemap guarantee indexing?
No. It helps Google find the URL; whether it is indexed depends on the page.
Install
npx shadcn@latest add https://seoforsaas.dev/r/article.jsonYou get components/seoforsaas/ and lib/seoforsaas/types.ts, as source files in your repository: no package to keep updated, no dependency beyond React (tested with React 19). The components import the types relatively, so keep the two folders side by side. Customize through components and CSS variables instead of editing the files, so reinstalling a newer version stays a clean overwrite.
Render
import { Article } from "@/components/seoforsaas/article";import { JsonLd } from "@/components/seoforsaas/json-ld";import "@/components/seoforsaas/article.css";<h1>{article.title}</h1><Article blocks={article.blocks} /><JsonLd data={article.seo.jsonLd} />The title isn't a block: render it as the page's h1. Only video.tsx is a client component (click to play); everything else renders on the server, and without JavaScript the video's poster links to YouTube.
Replace a block
components merges over the defaults: you replace only what you pass, and each replacement receives its block, typed.
import Image from "next/image";import { Article } from "@/components/seoforsaas/article";import { safeHref } from "@/components/seoforsaas/rich-text";import type { CtaBlock, ImageBlock } from "@/lib/seoforsaas/types";function SignupCta({ block }: { block: CtaBlock }) { const href = safeHref(block.buttonHref); // the same link check the defaults use return ( <section className="signup-cta"> <h2>{block.heading}</h2> {block.body && <p>{block.body}</p>} {href && <a href={href}>{block.buttonLabel}</a>} </section> );}function ArticleImage({ block }: { block: ImageBlock }) { // Set the size your layout needs. return <Image src={block.url} alt={block.alt} width={1536} height={1024} />;}<Article blocks={article.blocks} components={{ cta: SignupCta, image: ArticleImage }} />With next/image, allow the SEO for SaaS CDN in next.config: images: { remotePatterns: [new URL("https://media.seoforsaas.dev/**")] }.
Block types added later
A type the components don't know renders through components.unknown, which renders nothing. Your pages never break when we add one. To log unknown types in development:
import { Article } from "@/components/seoforsaas/article";<Article blocks={article.blocks} components={{ unknown: ({ block }) => { if (process.env.NODE_ENV === "development") console.warn(`Unrendered block type: ${block.type}`); return null; }, }}/>Theming
Everything is scoped under .sfs-article and driven by CSS variables. The defaults derive from currentColor, so the article reads well on light and dark pages as installed, and the stylesheet restores list markers and table borders a CSS reset removes.
.sfs-article { --sfs-font: "Inter", sans-serif; --sfs-heading-font: "Bricolage Grotesque", sans-serif; --sfs-accent: #7c3aed; /* links, quote rule, CTA */ --sfs-accent-contrast: #ffffff; /* text on the CTA */ --sfs-radius: 16px; --sfs-space: 1.25em; /* rhythm between blocks */}| Variable | Default | Used by |
|---|---|---|
--sfs-font, --sfs-heading-font, --sfs-mono-font | inherit, inherit, system mono | text, headings, code |
--sfs-text, --sfs-muted | currentColor, 65% of it | body, captions |
--sfs-border, --sfs-surface | 14% and 5% of currentColor | tables, callouts, code |
--sfs-accent, --sfs-accent-contrast | teal, white | links, CTA |
--sfs-radius, --sfs-space | 12px, 1.25em | corners, spacing |
Every block
Each block has a page with a live render, its JSON and how to replace it: heading, paragraph, list, quote, code, image, callout, call to action, FAQ, table, video.