Dynamic pages (Catch-All)
You want a list under /blog and the article under /blog/mein-artikel – without creating a separate page for each article? That’s what Catch-All is for.
- Create the page
/blogand check Catch-All under Settings. - Create a field
slugin the Collection blog (the URL part of each article). - HTML of the page
/blog:
{% if route.slug %}
{% with post=item("blog", "slug", route.slug) %}
{% if not post %}{{ not_found() }}{% endif %}
<article>
<h1>{{ post.titel }}</h1>
<p class="datum">{{ post.datum }}</p>
{{ image(post.bild, "(min-width: 900px) 60vw, 100vw") }}
{{ post.text|markdown }}
<a href="/blog">← Alle Artikel</a>
</article>
{% endwith %}
{% else %}
<h1>Blog</h1>
{% for post in data("blog", "sort", "-datum") %}
<a class="teaser" href="/blog/{{ post.slug }}">
<h2>{{ post.titel }}</h2>
<p>{{ post.text|truncatechars:160 }}</p>
</a>
{% endfor %}
{% endif %}
- SEO title of the page:
{% if route.slug %}{{ item("blog", "slug", route.slug).titel }} – {% endif %}Blog
How it works:
/blog→route.slugis empty → list./blog/mein-artikel→ there is no pagemein-artikel→ the Catch-All page responds withroute.slug = "mein-artikel"./blog/gibts-nicht→item()finds nothing →not_found()returns the 404 page with status 404.- Real subpages take precedence: If you create
/blog/archivas a normal page, that one is shown. - Multi-level paths:
/blog/2025/rueckblickresults inroute.slug = "2025/rueckblick",route.rest.0 = "2025",route.rest.1 = "rueckblick".
For testing, the preview of Catch-All pages has the Subpath field. If the template calls not_found() (e.g. without a subpath), the preview shows a notice with existing subpaths instead of an error – from the sitemap source or the detected Collection.
Sitemap: If the HTML, the SEO title or the Head field of the Catch-All page contains item("<collection>", "<feld>", route.slug), IllusionCMS adds all published entries of this Collection to /sitemap.xml as detail pages (not drafts and not entries without a value in the field). Real subpages take precedence.
Detail pages from other sources: If the page reads its data differently – from IllusionDB, a REST source or via a Workflow –, you set the source under Settings of the Catch-All page at Detail pages for sitemap and static export:
- IllusionDB table: connection, table, the column with the URL part (e.g.
slug), optionally a column with the modification date (<lastmod>), and rules or a saved view as a filter – e.g. onlyaktiv = true. - Workflow: the HTTP input of a Workflow (as with
workflow()) that returns a list – objects with the URL field or simply texts. It receivessitemap=1inmsg.payload. This way REST sources and computed lists also get into the sitemap. - Check shows how many detail pages result, and the first addresses. Values with
/result in multi-level paths (berlin/zahnarzt→/leistungen/berlin/zahnarzt). - From IllusionDB, only the column with the URL part and the date are queried.
- The list is cached for 10 minutes. If the source is ever unreachable, the last state stays in the sitemap instead of thousands of addresses disappearing.
- From 50,000 addresses on,
/sitemap.xmlautomatically becomes a sitemap index with/sitemap-1.xml,/sitemap-2.xml, … (the search engines’ limit per file). - The static export includes the same detail pages – even if nothing links to them.
Data from IllusionDB: Detail pages can also fetch their records directly from a database – via a Workflow with the IllusionDB node, see Workflows → IllusionDB.