SEO Optimization
Lila provides powerful built-in tools to manage Search Engine Optimization (SEO) metadata, automatically
inject it into your templates, and generate necessary files like sitemap.xml.
1. The @seo Decorator
The easiest way to define metadata for a specific route is using the @seo decorator from lila.core.routing (re-exported from lila.core.seo). It stores the merged metadata in request.state.seo; render() exposes it as the
template seo object with
title, description, keywords,
canonical, robots and author.
from lila.core.routing import Router, seo
from lila.core.templates import render
router = Router()
# Basic usage: any keyword becomes a key of the seo object.
@router.get("/")
@seo(
title="Home Page",
description="Welcome to our site",
keywords="python, lila, framework",
canonical="https://mywebsite.com/",
author="Lila Team",
robots="index, follow",
)
async def index(request):
return render(request, "index")
# Multilingual values: a dict is resolved at request time with Translate.lang().
@router.get("/about")
@seo(title={"en": "About Us", "es": "Sobre Nosotros"})
async def about(request):
return render(request, "about")
2. Centralized SEO Configuration
SEO_CONFIG is a plain dict in lila.core.seo.
Populate it (for example in your app package) before requests are handled; the decorator
merges SEO_CONFIG[request.url.path] first and the decorator keywords override
it.
from lila.core.seo import SEO_CONFIG
# Map route paths to their SEO metadata.
SEO_CONFIG.update({
"/": {
"title": "Global Home Page",
"description": "This applies to the root path automatically.",
},
"/services": {
"title": {"en": "Services", "es": "Servicios"},
},
})
# main.py — import the module once so SEO_CONFIG is filled.
import app.seo # noqa: F401
from lila.core.app import App
app = App(routes=[router])
3. Template Integration
The default layout already renders everything through lila/head.jinja:
<title> (formatted with TITLE_TEMPLATE), description, keywords, author,
canonical, robots, Open Graph/Twitter tags, favicon and JSON-LD. Values from @seo/SEO_CONFIG
can be overridden per call: render(request, "page", title="...", description="...", image="img/og.png",
canonical=True, jsonld={...}). Precedence: .env < SEO_CONFIG/@seo <
context < render() keywords. In a custom layout include
{% include "lila/head.jinja" %}, or use the seo dict directly as below:
<head>
<!-- seo is a dict; title/description fall back to the base context -->
<title>{{ seo.title | default(title) }}</title>
<meta name="description" content="{{ seo.description | default(description) }}">
<meta name="keywords" content="{{ seo.keywords | default(keywords) }}">
{% if seo.canonical %}<link rel="canonical" href="{{ seo.canonical }}" />{% endif %}
{% if seo.robots %}<meta name="robots" content="{{ seo.robots }}" />{% endif %}
<meta name="author" content="{{ seo.author | default(author) }}">
</head>
4. Generating Sitemap & Robots
lila seo reads APP_URL (or --domain), imports
main:app, discovers public GET routes (skipping dynamic paths,
/api, /admin, /debug, /uploads,
/auth/ and the auth pages) and writes public/sitemap.xml plus
public/robots.txt.
Both files are served at the site root by App:
/sitemap.xml and
/robots.txt — not under /public/.
# Both files at once (uses APP_URL from .env)
lila seo
# Only one of them, with an explicit domain
lila seo sitemap --domain https://mywebsite.com
lila seo robots --domain https://mywebsite.com
# Extra paths for the sitemap
lila seo sitemap --paths "/pricing,/blog"