Technical SEO is the foundation that makes your content visible, crawlable, and understandable to search engines. And when you’re working with a headless CMS like Strapi, that foundation depends on how well your frontend, API, and infrastructure work together.
Strapi gives developers a powerful, flexible way to structure and deliver content, but that flexibility means the responsibility for SEO shifts further into your hands. From URL structure to metadata, performance to pre-rendering, getting technical SEO right requires thoughtful implementation.
This guide walks through the key technical SEO tactics you can apply directly to Strapi projects with code-level examples, recommended configurations, and tips to avoid common traps in headless setups.
In brief:
- Strapi’s API-first approach gives developers full control over SEO-critical elements like URLs, metadata, and structured data.
- Server-side rendering (SSR) or pre-rendering is essential to ensure your Strapi-powered content is crawlable by search engines.
- Canonical URLs, hreflang tags, and robots meta directives can be modeled and served directly from Strapi.
- The official SEO plugin supports automatic sitemap generation and metadata management across content types.
- Strapi v5 adds improved API performance, relationship modeling, and extensibility to make scaling technical SEO easier.
1. Make Content Crawlable with Server-Side or Pre-rendered HTML
Search engines can’t reliably index JavaScript-rendered content. If your Strapi project delivers content through a frontend built with React, Vue, or another SPA framework, and you're using client-side rendering (CSR), there’s a good chance bots are missing the actual page content. That means your pages won’t rank, or worse, won’t get indexed at all.
Serving pre-rendered or server-rendered HTML is a non-negotiable technical SEO baseline for Strapi-based projects. Without it, your content likely won’t be crawled correctly, no matter how good your metadata, URLs, or structured data are. SSR and SSG are the most reliable approaches, and modern frameworks make them easy to integrate with Strapi APIs.
To implement this, you have three main options: server-side rendering (SSR), static site generation (SSG), or pre-rendering for bots.
Use Server-Side Rendering for Dynamic, SEO-Critical Pages
If your site has frequently updated content, personalized pages, or preview modes, SSR is the best way to ensure bots get consistent HTML output.
In Next.js, that means using getServerSideProps
to fetch data from Strapi on each request:
1export async function getServerSideProps({ params }) {
2 const res = await fetch(`${process.env.STRAPI_API}/api/articles?filters[slug][$eq]=${params.slug}&populate=*`);
3 const data = await res.json();
4
5 return {
6 props: {
7 article: data.data[0],
8 },
9 };
10}
With SSR enabled, your frontend fetches and renders the content on the server, then sends the complete HTML to the client and the crawler.
Use Static Site Generation for Content That Changes Infrequently
If your content doesn’t change often — like documentation, product listings, or blog posts — use static site generation to pre-build your pages at deploy time. This is fast, cacheable, and reliable for SEO.
In Next.js:
1export async function getStaticPaths() {
2 const res = await fetch(`${process.env.STRAPI_API}/api/articles`);
3 const articles = await res.json();
4
5 return {
6 paths: articles.data.map((article) => ({
7 params: { slug: article.attributes.slug },
8 })),
9 fallback: false,
10 };
11}
12
13export async function getStaticProps({ params }) {
14 const res = await fetch(`${process.env.STRAPI_API}/api/articles?filters[slug][$eq]=${params.slug}&populate=*`);
15 const data = await res.json();
16
17 return {
18 props: {
19 article: data.data[0],
20 },
21 };
22}
Every article page is rendered to static HTML at build time. Search engines receive fully formed content instantly, with no rendering delay.
Use Pre-Rendering for Bots If SSR or SSG Aren’t Available
If your architecture doesn’t allow for SSR or SSG, use a headless browser (like Puppeteer) to pre-render pages for bots only. This requires detecting bots by user-agent and serving a rendered snapshot of your app.
A basic middleware example:
1const isBot = (userAgent) =>
2 /Googlebot|Bingbot|Slurp|DuckDuckBot|Ba?iduspider/i.test(userAgent);
3
4if (isBot(req.headers['user-agent'])) {
5 const html = await fetchPreRenderedVersion(req.url);
6 return res.send(html);
7}
You can generate pre-rendered HTML using tools like Rendertron or Puppeteer, then cache those outputs and serve them selectively.
This method is harder to maintain, but it can work as a fallback if your stack is locked into client-rendered pages.
2. Use Canonical URLs to Prevent Duplicate Indexing
When your content is accessible from multiple URLs through filters, paginated routes, localized versions, or previews search engines may treat each variant as a separate page. That splits ranking signals and risks duplicate content issues. To prevent this, your frontend needs to explicitly declare a canonical URL for every indexable page.
In Strapi, the best way to manage this is through a reusable SEO component. Add a canonicalUrl
field to that component so editors can override the canonical when needed — for example, if two content types share similar text or if a page is accessible via both a slug and a query parameter. Otherwise, default to the standard path for that content type.
In your frontend (for example, with Next.js), you can conditionally render the canonical tag like this:
1const canonical = seo.canonicalUrl
2 ? seo.canonicalUrl
3 : `https://yourdomain.com/articles/${article.slug}`;
You can also inject this HTML code into the page head:
1<link rel="canonical" href={canonical} />
This makes sure even if a page is loaded with a ?ref=
or ?utm=
query string, it always points search engines to the clean canonical version. If you're managing multiple locales, each language variant should point to its own canonical URL, not a shared root, and be paired with an hreflang
implementation later in your SEO stack.
Done properly, canonical URLs ensure that your site doesn’t compete with itself in search results. In a headless architecture like Strapi, where routing is decoupled and content is often reused across different contexts, canonical control is critical for maintaining clean, unified SEO performance.
3. Reduce API Latency to Improve Largest Contentful Paint
When you use Strapi as a headless CMS, your frontend depends on the API to deliver content at runtime or build time. If your Strapi API responds slowly, that delays when key page elements are rendered, especially the hero text, product images, or article titles that make up the Largest Contentful Paint (LCP). Since LCP is a Core Web Vital and a direct ranking factor, keeping your content API fast is essential.
The best way to improve API response times is through strategic caching, ideally at multiple layers.
Start at the infrastructure level. Use a reverse proxy like Cloudflare, NGINX, or Fastly to cache Strapi’s GET requests. This allows you to serve popular content instantly from edge nodes without hitting the backend on every request. For example, cache article or product endpoints that don’t change often.
At the application level, Strapi doesn’t include built-in response caching, but you can easily add it using the Strapi REST Cache Plugin. It supports memory and Redis backends.
Use the following code to install the plugin:
1npm install strapi-plugin-response-cache
Then enable and configure the plugin in config/plugins.js
. Here’s an example using in-memory caching for development:
1// config/plugins.js
2module.exports = {
3 'response-cache': {
4 config: {
5 provider: 'memory', // Use 'redis' for production
6 maxAge: 600000, // 10 minutes
7 },
8 },
9};
In production, swap out the provider for Redis and configure your cache server credentials. This will reduce the load on Strapi and speed up repeated API requests for public content.
To keep cached data accurate, configure Strapi webhooks that run on content updates. These can trigger revalidation or cache purges for specific API routes or frontend pages. You can create webhook listeners using your frontend framework or edge function provider.
If your frontend is deployed on Vercel, Netlify, or a custom CDN, take advantage of edge caching and incremental static regeneration (ISR) to reduce build frequency and serve updated content without waiting for a full redeploy.
Finally, pre-warm your cache. After publishing content, automatically request high-priority routes like your homepage or blog index. This ensures they’re already cached the next time a user or search engine bot hits your site.
4. Generate and Serve an XML Sitemap
Strapi doesn’t render pages, so unless you explicitly tell search engines what URLs exist, many of your pages may never be indexed. An XML sitemap lists every route that should be discoverable and how often it changes.
The easiest way to generate one is with the Strapi SEO plugin, which supports automatic sitemap generation for your published content. Once installed and configured, it automatically detects content types and builds a dynamic sitemap.xml
route.
For more control, you can build a custom sitemap endpoint. Query your published content via the Strapi API and return well-formed XML manually or using a library like xmlbuilder2
, like so:
1// Example: sitemap generation route in Strapi
2router.get('/sitemap.xml', async (ctx) => {
3 const pages = await strapi.entityService.findMany('api::page.page', {
4 filters: { publishedAt: { $notNull: true } },
5 fields: ['slug', 'updatedAt'],
6 });
7
8 const xml = buildSitemapXml(pages);
9 ctx.type = 'application/xml';
10 ctx.body = xml;
11});
To keep your sitemap current, trigger regeneration using lifecycle hooks or content publishing webhooks. Then, submit the sitemap to Google Search Console and Bing Webmaster Tools to monitor indexation over time.
5. Implement hreflang Tags for Multilingual Sites
If your Strapi site uses the Internationalization (i18n) plugin, you need to output hreflang
tags on your frontend. These tags help search engines serve the correct language version to each user and avoid treating translated pages as duplicate content.
Each time your frontend loads a page, it should fetch all localized variants from Strapi and inject corresponding <link rel="alternate" hreflang="...">
tags in the <head>
.
1<link rel="alternate" href="https://example.com/en/about" hreflang="en" />
2<link rel="alternate" href="https://example.com/fr/about" hreflang="fr" />
3<link rel="alternate" href="https://example.com/about" hreflang="x-default" />
You can fetch all translations of a single entry using the populate=localizations
parameter in your Strapi API call. Be sure to output a x-default
link for fallback behavior.
Correct hreflang implementation improves international SEO and ensures users land on the right version of your content.
6. Use JSON-LD Structured Data Based on Strapi Content
Structured data helps search engines interpret your content more accurately by defining its purpose and type, for example, whether a page is a product, article, recipe, or event. When implemented well, it can provide enhanced visibility in search results through rich snippets, carousels, and other visual features that improve click-through rates.
With Strapi, you can implement structured data using JSON-LD in a few practical ways, depending on your stack and workflow. The most straightforward approach is to manually embed structured data in your frontend. After retrieving content from Strapi’s API, you generate the appropriate JSON-LD object and inject it into the ```<head>
``` of your page. For example:
1<script type="application/ld+json">
2{
3 "@context": "http://schema.org",
4 "@type": "Product",
5 "name": "My Great Hypothetical Widget",
6 "image": "http://www.example.com/image.jpg",
7 "description": "A widget of great hypothetical value."
8}
9</script>
This works well if you only need to structure a few content types or want full control over what gets rendered.
For teams managing multiple types of content or large-scale sites, automation becomes essential. The Strapi Generate Schema Plugin can help by automatically mapping your content types to Schema.org standards. Once installed and configured, it lets you define schema mappings via the admin UI, then access the resulting JSON-LD by appending ```?schemaOrg=true``` to API requests. This is a great way to ensure consistency across your site and reduce the manual effort of maintaining schema definitions.
If you want even tighter integration, you can also extend Strapi’s models directly to include structured data fields within your content types. This allows you to store and expose metadata like ```@context
```, ```@type
```, or product-specific attributes alongside your main content, making it easy to serve valid structured data directly from your API.
Regardless of which method you use, keep a few best practices in mind:
- Align your content types with Schema.org entities (e.g.,
Article
,Product
,Event
) that reflect what your content actually represents. - Automate structured data generation wherever possible to reduce human error.
- Serve structured data consistently on both static and dynamic pages.
- Validate your markup with tools like Google’s Rich Results Test to catch formatting or logic issues.
- Keep your schemas updated as standards evolve, and avoid using schema types purely for SEO gain if they don't apply to your content.
One example of this in action: an e-commerce platform using Strapi mapped their "Product" collection directly to the Schema.org Product
type, including fields like name, image, price, and availability. This allowed their product pages to qualify for visual SERP enhancements like product carousels, which in turn helped increase visibility and drive more qualified traffic.
Structured data is foundational to how modern search engines parse and prioritize your content. With Strapi, you have the flexibility to implement it in a way that fits both your content model and your tech stack.
7. Prevent Crawling of API and Preview Routes
By default, Strapi exposes internal data through routes like /api/articles
or /graphql
, which are not meant for public discovery. You may also expose preview or draft routes for editorial use. If search engines crawl and index these, you risk duplicate content, staging leaks, and overall SEO dilution.
To lock these down properly, do the following:
First, set noindex headers on server responses. Add X-Robots-Tag: noindex
to any route that shouldn’t be indexed, including API routes, staging subdomains, and preview pages. If you’re using NGINX or a CDN, you can use the following:
1location /api/ {
2 add_header X-Robots-Tag "noindex, nofollow";
3}
If your frontend uses Next.js, you can set the equivalent meta tag on preview pages:
1<meta name="robots" content="noindex, nofollow" />
Next, block unwanted paths using your robots.txt
. You control this file manually in most frontend frameworks. Add a rule like:
1User-agent: *
2Disallow: /api/
3Disallow: /preview/
4Disallow: /draft/
Lastly, avoid exposing staging environments. Set up basic auth, IP whitelisting, or a full noindex
policy for any pre-production domains. Bots often find and crawl them, even without links, unless you actively block access.
These steps help keep search engines focused on canonical content and prevent accidental indexing of raw data, internal tools, or incomplete pages.
8. Standardize Slugs and URL Structure Across All Content
Strapi gives you full control over your URLs, which is powerful, but risky if left unmanaged. Without conventions, you’ll end up with inconsistent slugs, poorly formed URLs, or unintentional duplicates.
Here’s how to standardize slug behavior and enforce SEO-friendly URLs:
First, create a dedicated slug
field in every public-facing content type. This lets editors define custom URLs where needed, but also gives you a place to apply automated logic.
Next, auto-generate slugs using lifecycle hooks. Set default slugs based on the title if one isn’t manually provided. Example for an Article:
1// ./src/api/article/content-types/article/lifecycles.js
2module.exports = {
3 beforeCreate(event) {
4 const { data } = event;
5 if (!data.slug && data.title) {
6 data.slug = data.title.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, '');
7 }
8 },
9};
Then, normalize slugs across environments. Use validation or middleware to prevent uppercase letters, special characters, or spaces. Stick to lowercase, hyphen-separated words for consistency.
Also, make sure to reflect content hierarchy in URL patterns. For example:
- Blog article →
/blog/what-is-headless-cms
- Docs page →
/docs/getting-started/configuration
This improves internal linking, context, and crawl efficiency. In your frontend routing logic, generate URLs based on content relationships stored in Strapi (e.g., category → article).
Bonus: Protect legacy links. If you’re migrating from a monolithic CMS, set up 301 redirects for any old URLs that don't match the new pattern. Store legacy URLs in a field like oldSlug
and resolve them server-side.
Supercharge Your SEO Strategy with Strapi
Strapi offers a powerful foundation for technical SEO, with Strapi v5 providing even more robust capabilities for developers. Implementing these six technical SEO techniques can help you create a better, faster, and more accessible digital experience that performs well in search engines.
Remember that Strapi gives you complete flexibility in implementing SEO strategies, allowing you to craft a custom approach that fits your specific project requirements. This flexibility does require more deliberate planning than traditional CMS platforms, but the payoff is greater control and optimization potential.
Additionally, consider referring to an SEO checklist for developers to ensure you cover all essential aspects of your implementation.
Stay informed about emerging technical SEO trends and leverage Strapi's API-first architecture to adapt quickly to search engine algorithm changes. For more tools to enhance your Strapi implementation, explore the Strapi Market for plugins that can further streamline your SEO workflow. You can also use Strapi Cloud to host your web applications.