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.

Using @seo

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.

app/seo.py

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:

resources/html/lila/base.jinja

<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/.

CLI

# 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"