Pelican SEO audit
Pelican generates clean static HTML — ideal for performance and crawlability. The SEO gaps are in configuration: SITEURL in publishconf.py for absolute canonical tags, pelican-sitemap for sitemap.xml, Jinja2 template additions for JSON-LD, and robots.txt via STATIC_PATHS. Run a free audit to see exactly what needs attention on your Pelican site.
Audit your Pelican site free →What DeepSEOAnalysis checks on Pelican sites
pelicanconf.py and publishconf.py meta tag coverage in Jinja2 templates
Pelican's meta tag output is controlled entirely by the active theme's Jinja2 templates (base.html, article.html, page.html, index.html). The audit validates whether all critical SEO meta tags are present in the rendered HTML: whether the <title> tag uses article.title or page.title from the content's frontmatter (not just the site-wide SITENAME); whether meta description is populated from article.summary, article.description frontmatter, or a summary_max_length-truncated excerpt (Pelican does not auto-generate descriptions — the template must explicitly include them, and content authors must fill the :summary: or :description: frontmatter field); whether OG meta tags (og:title, og:description, og:image, og:url, og:type) are present with absolute URLs in og:image; whether canonical tags use SITEURL + article.url or page.url (not just a relative URL); whether Twitter Card meta tags are present; and whether the RELATIVE_URLS setting is False in publishconf.py (the production config). RELATIVE_URLS = True is the pelicanconf.py default for local development — it must be overridden in publishconf.py to ensure all generated URLs are absolute. The audit also checks for meta tags on index pages (the blog index and category/tag archives) — these often lack meta descriptions and use the site's default SITENAME without any page-specific context.
URL structure and ARTICLE_URL / PAGE_URL configuration
Pelican generates URL slugs from the article or page filename (filename.html → /filename.html by default) or from the slug frontmatter field when specified. The audit validates: whether ARTICLE_URL and ARTICLE_SAVE_AS are configured for clean permalink patterns (default is {slug}.html; configuring to {slug}/index.html produces clean /slug/ paths); whether PAGE_URL and PAGE_SAVE_AS produce clean paths for static pages; whether RELATIVE_URLS is False in publishconf.py to ensure all generated links and canonical tags use absolute paths; whether the slug field is populated in article frontmatter (rather than relying on filename-derived slugs, which are harder to control for keyword quality); whether category and tag archives have been configured to use meaningful URL patterns (CATEGORY_URL = "category/{slug}/" vs default); and whether Pelican's automatic redirect of date-based paths or old slugs is configured (Pelican generates no redirects by default — if articles are renamed, the old URL 404s unless .htaccess or server-level redirects are added). The audit also checks for URL quality: slug fields that use spaces, underscores, or excessive keyword repetition negatively affect both SEO and usability.
JSON-LD structured data via Jinja2 article.html and page.html templates
Pelican generates no structured data by default. JSON-LD must be added to Jinja2 templates. The audit validates what JSON-LD is present in the rendered page HTML, whether it is server-rendered in the initial static HTML response (not client-side, which would be unusual for a static site generator but possible if JS is injecting it), and whether required properties are present. For Article schema on blog posts: @context, @type: "Article", headline (from article.title), datePublished (from article.date.isoformat() — must be ISO 8601 format like "2026-07-13"), author with @type: "Person" and name, and image (absolute URL required — og:image value is commonly reused). For FAQPage schema: @type: "FAQPage", mainEntity array with Question and Answer pairs — useful for how-to or guide content. Common errors on Pelican Article schema: using article.date directly without .isoformat() (Python datetime vs string), building the image URL without SITEURL prefix (producing a relative path), and omitting the image property entirely (required by Google for Article rich results). The pelican-seo plugin can generate some structured data automatically from pelicanconf.py settings, reducing the template work required.
pelican-sitemap plugin, robots.txt, and static file configuration
Pelican requires the pelican-sitemap plugin for sitemap.xml generation. The audit validates: whether the sitemap plugin is installed and configured in PLUGINS; whether the generated sitemap.xml is accessible at /sitemap.xml on the deployed site; whether sitemap entries use absolute URLs (SITEURL must be set); whether thin pages (category archives, tag archives, author pages, pagination pages) are excluded from the sitemap using the SITEMAP exclude configuration; whether sitemap <lastmod> dates reflect article publication or modification dates rather than just the build date; and whether a robots.txt is configured. robots.txt in Pelican requires explicit configuration: create the robots.txt file in the Pelican content directory, then add the static extra configuration in pelicanconf.py: STATIC_PATHS = ["extras"] and EXTRA_PATH_METADATA = {"extras/robots.txt": {"path": "robots.txt"}}. This tells Pelican to copy extras/robots.txt to the root of the output directory. The robots.txt should include a Sitemap: declaration pointing to the absolute sitemap URL, and should explicitly allow AI crawlers (GPTBot, ClaudeBot, PerplexityBot) rather than relying on implicit allow-all.
Core Web Vitals for Pelican static sites
Pelican sites generate pure static HTML — this is their performance advantage. Serving pre-rendered HTML with no server-side processing means excellent Time to First Byte (TTFB), typically 20–50ms from a CDN. The audit validates Core Web Vitals via CrUX field data (real user p75 measurements from Chrome). Common performance issues despite the static foundation: (1) LCP image loading — the largest visible element on the page (usually a hero image or article thumbnail) is often not preloaded. Adding <link rel="preload" as="image" href="{{ article.og_image }}"> in the article.html <head> and fetchpriority="high" on the img element itself reduces LCP. (2) Google Fonts FOUT — themes that load fonts from fonts.googleapis.com trigger a Flash of Unstyled Text. Add font-display: swap to the font-face declaration or self-host fonts via the static files configuration. (3) CLS from images without explicit dimensions — article images without width and height attributes in the Jinja2 template cause Cumulative Layout Shift as the browser doesn't know the image's dimensions before loading. Add explicit width/height or aspect-ratio CSS to image elements in templates. (4) INP risks from third-party scripts — analytics and commenting widgets (Disqus) are the primary INP risk on static Pelican sites; core Pelican generates minimal JavaScript.
AI visibility and GEO signals for Pelican sites
Pelican's static HTML output is ideal for AI crawler access — no JavaScript rendering required, fast response times, and clean semantic HTML. The audit checks five AI visibility signals: (1) AI crawler access in robots.txt — the robots.txt must not block GPTBot, ClaudeBot, PerplexityBot, or OAI-SearchBot. Verify the User-agent: * Allow: / directive doesn't coexist with specific crawler blocks. Configure robots.txt via Pelican's STATIC_PATHS and EXTRA_PATH_METADATA settings. (2) llms.txt presence — place llms.txt in the same static extras directory as robots.txt and configure the same EXTRA_PATH_METADATA pattern to copy it to the output root. llms.txt should list the most authoritative content on the site in plain text with absolute URLs. (3) FAQPage JSON-LD server-rendered in initial HTML — add FAQPage schema directly in article.html or page.html Jinja2 templates for content that covers question-and-answer topics. Static sites have no client-side rendering risk — all schema in templates is server-rendered in the initial response. (4) Question-heading ratio — measure whether ≥20% of H2/H3 headings in article content are phrased as questions; this is a content authoring guideline rather than a Pelican-specific configuration. (5) Content chunkability — average section length ≤400 words per H2/H3 section; review article templates for any layout patterns that would merge multiple topics under a single heading.
Pelican SEO setup workflow
- Set SITEURL in publishconf.py — ensures all canonical tags, OG URLs, JSON-LD identifiers, and sitemap entries use absolute URLs. RELATIVE_URLS must be False in publishconf.py.
- Install pelican-sitemap — add "sitemap" to PLUGINS in pelicanconf.py and configure SITEMAP to exclude thin taxonomy pages (tag/, category/, author/).
- Add canonical tags in base.html — use SITEURL + article.url or page.url in the Jinja2 template, inside the <head> block. Verify the rendered href starts with https://.
- Add JSON-LD to article.html — Article schema with headline, datePublished (article.date.isoformat()), author, and image (absolute OG image URL). FAQPage schema for guides and how-to content.
- Configure robots.txt and llms.txt — place both files in a static extras/ directory and configure STATIC_PATHS + EXTRA_PATH_METADATA to copy them to the output root. Include Sitemap: declaration in robots.txt.
- Add image dimensions and preload — set explicit width/height on article images to prevent CLS; add <link rel="preload"> for the LCP image in article.html; switch from CDN Google Fonts to self-hosted fonts.
Pelican SEO FAQ
- Does Pelican have good SEO support out of the box?
- Pelican provides a reasonable SEO starting point: it outputs clean static HTML from Jinja2 templates, supports rich frontmatter metadata via reStructuredText or Markdown, and generates clean URL paths. However, several critical SEO features require explicit configuration or plugins: (1) Canonical tags — not output by default; must be added to the theme\'s base.html (or templates/base.html) template manually using the SITEURL setting. (2) Sitemap.xml — not generated by default; requires the pelican-sitemap plugin. (3) JSON-LD structured data — not generated by default; requires Jinja2 template additions to the article.html and page.html templates. (4) robots.txt — not generated by default; must be placed in the Pelican content directory and configured via STATIC_PATHS and EXTRA_PATH_METADATA in pelicanconf.py. The pelican-seo plugin (available via pip) adds structured meta tags, OpenGraph support, and Twitter Card generation via configuration in pelicanconf.py — it significantly reduces the Jinja2 template work needed for a complete SEO setup.
- How do you add canonical tags to a Pelican site?
- Pelican canonical tags require two things: SITEURL set in publishconf.py (not just pelicanconf.py) and a template addition. Step 1: in publishconf.py (the production configuration file), set SITEURL = "https://yourdomain.com" (no trailing slash). SITEURL in pelicanconf.py is often left empty or as a development value — publishconf.py overrides it for production builds. Step 2: add the canonical tag to your theme\'s templates/base.html inside the <head> block: <link rel="canonical" href="{{ SITEURL }}/{{ output_file }}">. For article pages, use <link rel="canonical" href="{{ SITEURL }}/{{ article.url }}">. For page templates: <link rel="canonical" href="{{ SITEURL }}/{{ page.url }}">. Critical: if RELATIVE_URLS is set to True in pelicanconf.py (which is the default for local development), it must be False in publishconf.py or canonical URLs will be relative paths, which are invalid for canonical tags. Verify by inspecting the rendered HTML of a published page to confirm the canonical href starts with https://.
- How do you generate a sitemap.xml in Pelican?
- Pelican does not generate sitemap.xml by default. To add a sitemap: install the pelican-sitemap plugin via pip (pip install pelican-sitemap), then add "sitemap" to your PLUGINS list in pelicanconf.py: PLUGINS = ["sitemap"]. Configure the sitemap settings in pelicanconf.py: SITEMAP = {"format": "xml", "priorities": {"articles": 0.7, "indexes": 0.5, "pages": 0.6}, "changefreqs": {"articles": "monthly", "indexes": "daily", "pages": "monthly"}, "exclude": ["tag/", "category/", "author/"]}. The "exclude" list prevents taxonomy index pages (tag archives, category archives) from appearing in the sitemap if they\'re thin or auto-generated. The plugin generates the sitemap at the output path specified by SITEMAP_SAVE_AS (default: "sitemap.xml"), which means it appears at /sitemap.xml on the live site. After generation, verify the sitemap includes article and page URLs with the correct SITEURL base — if SITEURL is empty, URLs in the sitemap will be relative (invalid). Submit the sitemap URL to Google Search Console under Sitemaps after deployment.
- What are the most common SEO problems on Pelican static sites?
- The most common Pelican SEO issues: (1) RELATIVE_URLS = True in production — this is the default for pelicanconf.py (local development) but must be overridden to False in publishconf.py. If RELATIVE_URLS is True, all URLs in the generated HTML are relative, making canonical tags and JSON-LD @id values invalid. Run pelican content -s publishconf.py (not pelicanconf.py) for production builds. (2) SITEURL not set in publishconf.py — a missing SITEURL means absolute URLs cannot be generated for canonical tags, OG image URLs, sitemap entries, or JSON-LD. (3) No sitemap plugin — without pelican-sitemap, Googlebot must discover all pages through link-following alone. (4) Missing or incorrect canonical tags — using article.url without the SITEURL prefix generates relative canonical paths. (5) Google Fonts FOUT — themes that load Google Fonts from the CDN cause Flash of Unstyled Text. Use font-display: swap or preload the key font weights. (6) Tag and category pages included in sitemap — auto-generated taxonomy pages are thin content and should be excluded from the sitemap using the SITEMAP exclude configuration.