Serve your OMNIFAMOUS articles on a Lovable site

Your articles appear at yoursite.com/blog, rendered on the server. Search engines and AI crawlers read the full article, the title, the description, the canonical link and the structured data without running any JavaScript. When you edit or unpublish an article in OMNIFAMOUS, your site shows the change within about a minute. No rebuild, no copy and paste.

Run your free AI visibility auditNo credit card required. Results in minutes.

Live todayOne prompt adds a server rendered /blog to your Lovable app. Target keyword: lovable seo

What we do for you

A crawlable blog inside the Lovable app you already built

We do the work, not give you another dashboard. Here is what the engine does for your brand.

Adds /blog, /blog/<slug> and /blog/sitemap.xml to your Lovable project as server rendered TanStack Start routes, so crawlers get the full article in the raw HTML.

Fills every article page head from the content API: title, meta description, canonical, Open Graph, Twitter tags and JSON-LD structured data.

Reads a read only publishable key (pk_) on the server only, stored as a Lovable project secret. Visitors' browsers never see it.

Sanitizes each article on your server before it renders, and shows edits and unpublishes within about a minute with no rebuild.

How it works

From a new key to a Lovable blog that crawlers can read

Four steps. You stay the editor, the engine handles the rest.

1

Create a publishable API key in Settings, API, with no URL restriction, and copy your workspace ID.

2

Set your Blog URL in the dashboard to the address your blog will live at, so every canonical points at your site.

3

Paste the prompt below into Lovable, add the two secrets through Lovable's secure form, and publish.

4

Run the crawl check against the published site: every line should say PASS.

Step by step

The full Lovable guide

Every step, the exact prompt to paste, and how to check the result like a crawler.

It takes about 15 minutes and one prompt.

Before you start: is your project the right kind?

New Lovable projects render pages on the server, which is what this guide needs. Some older Lovable projects are client side apps: their pages are built in the browser, so crawlers see an empty page. Check yours first.

Quick check in the browser. Open your published site, right click, choose View page source, and search the source for a sentence you can see on the page. Then search for __TSR.

  • You find your text and __TSR: your project renders on the server. Continue with this guide.
  • You find neither, and the page body is mostly an empty <div id="root"></div>: it is a client side project. See Older client side projects at the end of this guide.

Or run the crawl check (needs Node 20 or newer, see step 6 for the download):

node crawl-check.mjs --detect-client-side https://yoursite.lovable.app

PASS server rendered means you are good to go. FAIL means it is a client side project.

1. Create a publishable API key

  1. In OMNIFAMOUS, open Settings, then the API tab.
  2. Under Publishable keys, click Create and give the key a name, for example Lovable blog.
  3. Leave URL Restrictions empty: do not add a URL. The key is only used from your server, which sends no browser origin, so a URL restriction would make the API refuse every request.
  4. Click Create API key and copy the key (it starts with pk_). Copy it now: you will not see it again.
  5. Copy your workspace ID.

A publishable key is read only: it sees your published articles and nothing else, never drafts, and cannot change anything. Keep it in server secrets anyway, out of chats, screenshots and code. The prompt below stores it as a project secret that never reaches your visitors' browsers.

2. Set your Blog URL

In the OMNIFAMOUS dashboard, set your Blog URL to the address your blog will live at, for example:

https://yoursite.com/blog

Use the domain you publish on: your custom domain if you have one, otherwise your lovable.app address. If the address you asked for is taken, Lovable picks another one when it publishes (for example focus-notes-field-guide.lovable.app): set the Blog URL to the address Lovable shows you after publishing. Every article's canonical link is built from this setting, so search engines credit your site, not ours.

3. Paste the prompt into Lovable

  1. Open your project in Lovable.
  2. Copy the prompt below. Replace <YOUR_WORKSPACE_ID> with your workspace ID and <YOUR_DOMAIN> with your domain (for example yoursite.com). Leave <A_REAL_SLUG> as it is, or replace it with the slug of one of your published articles.
  3. Paste it into the Lovable chat and send it.
Add a blog at /blog whose articles come from my headless content API and are rendered ON THE SERVER. Search engines and AI crawlers that do not run JavaScript must find the full article text, the title, the meta description, the canonical link, the Open Graph tags and the JSON-LD in the raw HTML of every article page. Edits and unpublished articles must show on the next request, with no rebuild. Follow every rule below exactly.

SECRETS (server only)
- Add two project secrets and ask me for their values with the secure secret form (I will not paste them in chat): OMNIFAMOUS_API_KEY (a read only publishable key that starts with pk_, used only from the server and kept as a secret anyway) and OMNIFAMOUS_WORKSPACE_ID (my workspace id is <YOUR_WORKSPACE_ID>). Optional: OMNIFAMOUS_API_URL, default https://api.omnifamous.com.
- Read them only inside server code: inside createServerFn handlers and server route handlers, through process.env. Never in a component, a hook, a VITE_ variable, a client bundle or a log line.
- Never fetch at module load. Every fetch and every secret read happens inside a handler, at request time. (A fetch at module load works in preview and makes the published site answer 500.)

CONTENT API (call it only from the server)
- Every request sends the header: Authorization: Bearer <OMNIFAMOUS_API_KEY>
- List: GET {API_URL}/v1/workspaces/{OMNIFAMOUS_WORKSPACE_ID}/content/articles?limit=24&cursor={cursor}
  Returns { articles: { items: [{ id, slug, title, excerpt, metaDescription, featuredImageUrl, publishedAt, updatedAt }], hasMore, nextCursor } }. Newest first, published articles only. To get the next page pass nextCursor back as cursor until hasMore is false (then nextCursor is null). limit is 1 to 100.
- One article: GET {API_URL}/v1/workspaces/{OMNIFAMOUS_WORKSPACE_ID}/content/articles/by-slug/{slug}
  Returns { article: { title, slug, contentHtml, featuredImageUrl, publishedAt, updatedAt }, seo: { meta: { title, description, canonicalUrl, ogTitle, ogDescription, ogImage, ogType, ogSiteName, twitterCard, twitterTitle, twitterDescription, twitterImage, publishedAt, updatedAt }, jsonLd, faqJsonLd, howToJsonLd, breadcrumbJsonLd, itemListJsonLd, reviewsJsonLd, hreflang: [{ hreflang, href }] } }. Any of faqJsonLd, howToJsonLd, breadcrumbJsonLd, itemListJsonLd can be null; reviewsJsonLd is null or an array.
- An unknown or unpublished slug answers 404. Any other non 200 answer is an error.
- Use a 10 second timeout (AbortSignal.timeout(10000)) and cache: "no-store".

FILES (TanStack Start)
1. src/lib/omnifamous.server.ts: the only file that reads the secrets and calls the API. Exports getArticlePage(slug), getArticleList(cursor), getAllArticles() (follows nextCursor for at most 50 pages of 100; if hasMore is still true after the 50th page it throws, so the sitemap answers 503 instead of a silently truncated list) and buildBlogSitemap(origin, articles). Route files load it with a dynamic import inside the handler: const { getArticlePage } = await import("../../lib/omnifamous.server").
2. src/routes/blog/$slug.tsx: a page route. A createServerFn({ method: "GET" }) calls getArticlePage. The loader validates params.slug against /^[a-z0-9][a-z0-9-]{0,200}$/ and throws notFound() when it does not match or when the API answers 404. head() sets, from seo.meta: <title>, meta description, og:title, og:description, og:type, og:site_name, og:url, og:image (when present), article:published_time, article:modified_time, twitter:card, twitter:title, twitter:description, twitter:image (when present), <link rel="canonical"> and one <link rel="alternate" hreflang> per hreflang entry, and one <script type="application/ld+json"> per non null JSON-LD block (jsonLd, faqJsonLd, howToJsonLd, breadcrumbJsonLd, itemListJsonLd, each reviewsJsonLd item). The route sets headers: () => ({ "cache-control": "no-store" }). The page shows the title as the only <h1>, the date, the featured image when present, and the sanitized contentHtml. notFoundComponent shows "Article not found" with a link to /blog, and head() adds <meta name="robots" content="noindex"> when there is no loader data.
3. src/routes/blog/index.tsx: the list page, newest first, 24 per page, an "Older articles" link to /blog?cursor={nextCursor} when hasMore. Validate the cursor search param against /^[A-Za-z0-9+/=_-]{1,512}$/ and ignore it otherwise. headers: () => ({ "cache-control": "no-store" }).
4. src/routes/blog/sitemap[.]xml.ts: a server route (createFileRoute("/blog/sitemap.xml")({ server: { handlers: { GET } } }), no component). It calls getAllArticles() and answers application/xml with <url><loc> for {origin}/blog and {origin}/blog/{slug} for every article (lastmod from updatedAt or publishedAt), where origin is new URL(request.url).origin. cache-control: public, max-age=60. On an API error answer 503 with retry-after: 120.
5. public/robots.txt: keep the existing lines and add: Sitemap: https://<YOUR_DOMAIN>/blog/sitemap.xml

SECURITY AND SEO RULES
- Sanitize contentHtml on the server, inside getArticlePage, with the sanitize-html package and an allowlist: the default allowed tags plus h1, h2, img, figure, figcaption, picture, source and iframe. Allowed attributes: a (href, name, title, target, rel), img (src, srcset, sizes, alt, title, width, height, loading, decoding), source (srcset, sizes, type, media), iframe (src, width, height, title, allow, allowfullscreen, frameborder, loading), th and td (colspan, rowspan, scope), ol (start, type, reversed), class on any tag, and id only on h2, h3 and h4 (for heading anchors; no id on other tags). Allowed URL schemes: http, https, mailto, tel; images may also use data:image/png, jpeg, gif, webp or avif. No protocol relative URLs. iframes only from www.youtube.com, youtube.com, www.youtube-nocookie.com and player.vimeo.com; drop other iframes. script and style elements are removed with their content; every on* attribute, style attribute and javascript: URL is removed. Add rel="noopener noreferrer" to links with target="_blank". If sanitize-html does not build in this runtime, use the xss package with the same allowlist; never render unsanitized HTML.
- Serialize JSON-LD with JSON.stringify and then replace < with \u003c, > with \u003e and & with \u0026, so no value can close the script tag.
- Use seo.meta.canonicalUrl exactly as given when it starts with http:// or https://; otherwise leave the canonical and og:url out. Do not invent a canonical from another host.
- Never send an x-robots-tag header and never add a robots noindex meta on a real article or on the list.
- Unknown slug, unpublished slug or a slug that fails the pattern: HTTP 404 with <meta name="robots" content="noindex">.
- Links to /blog and to articles from anywhere in the site are plain <a href="..."> elements, not router <Link>, so every blog page loads rendered from the server.
- If the project already has a /blog route, blog posts or posts data, STOP before changing anything and ask me whether to replace it or mount this blog at another path; never delete existing content without my answer.

When done, publish, then verify on the PUBLISHED site (not only the preview) that https://<YOUR_DOMAIN>/blog/<A_REAL_SLUG> answers 200 and its raw HTML (view source, no JavaScript) contains the article text, the <title>, the meta description, the canonical, og:title and the JSON-LD; that https://<YOUR_DOMAIN>/blog/this-slug-does-not-exist answers 404 with a robots noindex meta; and that https://<YOUR_DOMAIN>/blog/sitemap.xml lists the articles.

4. Add your secrets

Lovable asks for two secrets. Use the secure secret form it shows, not the chat:

Name Value
OMNIFAMOUS_API_KEY your publishable key (pk_...)
OMNIFAMOUS_WORKSPACE_ID your workspace ID

If Lovable does not ask, open your project settings, add them as secrets with exactly these names, and tell Lovable they are set.

5. Publish

Click Publish (or Update if the site is already live). The blog only works on the published site once Lovable has deployed the new code, so always check the live address, not just the preview.

6. Check your blog like a crawler

Open https://yoursite.com/blog and one article. Then check what a crawler sees.

With the crawl check (Node 20 or newer). Download crawl-check.mjs, then run:

node crawl-check.mjs https://yoursite.com/blog/your-article-slug
node crawl-check.mjs --expect-404 https://yoursite.com/blog/this-slug-does-not-exist
node crawl-check.mjs --sitemap https://yoursite.com/blog/sitemap.xml --contains https://yoursite.com/blog/your-article-slug
node crawl-check.mjs --robots https://yoursite.com

Every line should say PASS. The first command checks the status, the article text in the raw HTML, the title, the description, the canonical, Open Graph and the structured data, and that nothing tells search engines to skip the page.

Without Node, in a terminal:

curl -s -A Googlebot https://yoursite.com/blog/your-article-slug | grep -o '<title>[^<]*\|rel="canonical"[^>]*\|application/ld+json'

You should see your article title, a canonical link on your own domain and at least one application/ld+json.

Troubleshooting

What you see What to do
Blog pages show an error even though both values are set The publishable key has a URL restriction. Your server sends no browser origin, so the API refuses it. Create a new publishable key with URL Restrictions left empty and replace the OMNIFAMOUS_API_KEY value.
The published site answers 500 on every page, the preview works The code fetches something when it loads instead of per request. Tell Lovable: "The published site returns 500. Move every fetch and every secret read inside a server function or server route handler; nothing may run at module load."
Canonical points to the wrong address Fix your Blog URL in the OMNIFAMOUS dashboard (step 2). The change shows within about a minute.
Canonical check fails because the host differs You checked a different address than your Blog URL (for example the lovable.app address while the Blog URL uses your custom domain). Check the address in your Blog URL.
The article text is missing in the page source The page is rendered in the browser. Tell Lovable: "Article pages must be rendered on the server: load the article in the route loader through a createServerFn, not in a useEffect."
A new article does not show It must be published in OMNIFAMOUS, and it appears within about a minute of publishing. Drafts never appear on your site.
The blog sitemap answers 503 The kit could not read the full article list: check the two secrets (names and values). The kit's sitemap lists at most 5,000 articles: with more than 5,000 published articles it answers 503 instead of a partial list, until the content API adds a sitemap index.
404 on every article Check the two secrets (names and values) and your workspace ID, then publish again.

Older client side projects

If the check says your project renders in the browser, crawlers cannot read articles added this way. Create a new Lovable project: new projects render on the server. Move your pages there (you can ask Lovable to recreate your current design), then follow this guide.

Good to know

  • Only published articles appear. Unpublishing an article makes its page answer 404 within about a minute and removes it from your blog sitemap within about two minutes.
  • Your key stays on the server. It is read only and sees published articles only, and visitors' browsers never see it.
  • The kit sanitizes the article HTML on your server before your site shows it: scripts and unsafe links are removed, YouTube and Vimeo embeds are kept.
FAQ

Questions, answered

Everything you need to know about how the engine does the work for you.

Create a publishable OMNIFAMOUS API key, paste the prompt from this guide into Lovable and add OMNIFAMOUS_API_KEY and OMNIFAMOUS_WORKSPACE_ID as project secrets. Lovable adds server rendered /blog routes that read your published articles from the OMNIFAMOUS Content Delivery API on each request.

Yes. New Lovable projects render pages on the server, which is what this guide needs. Older client side projects build pages in the browser, so crawlers see an empty page: create a new Lovable project, move your pages there and then follow the guide.

A publishable key (it starts with pk_) with URL Restrictions left empty. It is read only and only sees published articles. The prompt stores it as a Lovable project secret that is read on the server, so it never reaches your visitors' browsers.

Within about a minute. The pages fetch on every request, and the OMNIFAMOUS API caches responses for up to 60 seconds. An unpublished article answers 404 within about a minute and leaves your blog sitemap within about two minutes. No rebuild is needed.

Yes. OMNIFAMOUS provides the article and its SEO data. Lovable renders the pages in your project with your own components and styles.

Still have questions? Talk to us

Publish everywhere

One engine, every destination

Write once, publish to every CMS your audience reads, and optimize for AI search at the same time.

See where your brand stands in AI search

Run a free AI visibility audit. We check your brand across ChatGPT, Claude, Gemini, and more, then write and publish the content that gets you cited.

Run my free auditNo credit card required. Results in under 5 minutes.