Pages and the page tree
Structure and URLs
The home page is the root of the tree and has the URL /. Every other page hangs below a parent page; its URL is made up of the slugs: page webdesign under leistungen → /leistungen/webdesign.
- Slugs consist of
a-z,0-9,.,_,-and start with a letter or digit. A slug must be unique under the same parent page. - Reserved slugs directly under the home page:
cms-admin,api,media,_assets,sitemap.xml,healthz,favicon.ico,robots.txt.
Multiple websites
One installation can run several websites – for example the company site and a shop, or sites for different brands. Each root page in the page tree is a website of its own with its own page tree, main menu (nav()), 404 page, sitemap and page cache.
- Create: In the page tree, + Website → name and domains (one per line, e.g.
shop.firma.deandwww.shop.firma.de). - Website icon (favicon): In the editor of the home page, under Settings → Website → Website icon, choose an image from the file manager – ideally an SVG or a square PNG of at least 180 × 180 px (then it also serves as the icon on the iPhone home screen). All pages of the website get the matching
<link rel="icon">, and/favicon.icoserves the icon. If a page sets an icon itself in the Head field, that one takes precedence. In the template, the URL is available as{{ site.favicon }}. - Domains of an existing website are changed in the editor of its home page under Settings → Website. A domain can belong to only one website. Scheme, port and path are removed when saving (
https://Shop.de:8443/x→shop.de). - Default website: Requests via domains not assigned to any website – including
localhostor the server’s IP address – show the default website. It is at the top of the page tree and carries the Default badge. - Shared are Snippets, components, CSS/JS files, files, Collections and REST sources. A header component can therefore run on several websites; with
{{ site.title }}or{% if site.id == 1 %}it can differ per website. - Viewing without a real domain: The path link at the top of the editor appends
?_site=<ID>. Signed-in admins then see this website – also while clicking on, until another one is chosen. For visitors the parameter has no effect, and a matching domain always takes precedence. - Testing locally with your own domain: Additionally enter a domain under
.localhost, e.g.shop.localhost. Browsers route everything under.localhostto your own machine without DNS and without a hosts file –http://shop.localhost:8080/then shows this website, even without signing in, i.e. the way visitors see it. You only specify the port in the address, not in the domains. Tools like curl don’t always resolve.localhoston their own; there,curl -H "Host: shop.localhost" http://127.0.0.1:8080/helps. - Delete a website: In the editor of its home page under Settings. All pages of the website are deleted; to be safe, you have to type in the name. The last website can be deleted too – afterwards every address returns the 404 page until you create one again.
For a domain to actually reach IllusionCMS, it must point to the server via DNS and be configured in the reverse proxy (see Operation).
Multilingual websites
Each language is a page tree of its own: a subtree of the same website (/en/…) or a separate website with its own domain (firma.de and firma.com). You tell IllusionCMS which language a tree has and which pages belong together – it then takes care of hreflang, the sitemap and <html lang> itself.
Setting the language: In the editor under Settings → Language – a BCP 47 code such as de, en, fr, de-at or pt-br (pick from the list or type freely). The language is inherited by all subpages without their own: set it once on the website (home page) and once on the home page of each language subtree, e.g. /en. Empty means “inherits from above”; the hint next to the field shows which language currently applies. If no page above has a language, de applies. In the page tree, pages with their own language show a small code.
The page automatically gets <html lang="…">; in templates the language is available as {{ page.lang }}. A lang= in the Attributes for <html> still takes precedence; when updating to this version it was copied once into the Language field.
Linking language versions: In the Language versions section of the settings → Link… → search pages of all websites by title, path or language and click the right one. Greyed out are pages in the same language, pages already linked and pages whose language already exists in the group – one page per language. If the chosen page is already linked with other versions, IllusionCMS asks whether to move it. The list shows language, title, path (for other websites with their name) and a link to open it. Unlink removes a version, Leave group removes the current page; if only one page is left, the group is dissolved. Deleting a page unlinks it as well. Linking uses the saved language – after changing the Language field, save first.
Automatic as soon as at least two published versions are linked:
- hreflang: The
<head>contains one<link rel="alternate" hreflang="en" href="https://firma.com/contact">per version (including the page itself). The addresses are absolute – via the first domain of the respective website, without a domain via the requested address. If the page’s head field already contains its ownrel="alternate" hreflangtags, IllusionCMS outputs none of its own. In addition,hreflang="x-default": if all versions are in the same website (language subtrees such as/and/de), it points to the version in that website’s language, otherwise to the one in the language of the default website (if there is one). - Sitemap: Every URL with versions gets
xhtml:linkentries withhreflang, also in the static export.
Drafts do not count.
Language switcher: translations() returns the published versions of the current page, sorted by language code, each with lang, title, url and current (the page itself). Without links the list contains only the current page – so the switcher can sit in the layout and stays empty wherever there is nothing to switch ({% if translations()|length > 1 %}). language_name(code) outputs the name of the language in that language:
<nav class="sprachen">
{% for v in translations() %}
{% if v.current %}<span aria-current="true">{{ language_name(v.lang) }}</span>
{% else %}<a href="{{ v.url }}" hreflang="{{ v.lang }}" lang="{{ v.lang }}">{{ language_name(v.lang) }}</a>{% endif %}
{% endfor %}
</nav>
Versions on another website link to its domain.
Menu per language: nav() always returns the subpages of the website’s home page – on /en/… it would show the German menu. lang_root() returns the home page of the current page’s language tree: the page itself or the nearest one above it with its own Language; if there is none, the website’s home page (also on catch-all detail pages and in the preview). This lets one shared layout build the right menu, including the home link, for every language:
{% with start=lang_root() %}
<a class="logo" href="{{ start.path }}">{{ site.title }}</a>
<nav>{% for p in children(start.id) %}<a href="{{ p.path }}">{{ p.title }}</a>{% endfor %}</nav>
{% endwith %}
If the English tree sits below the German home page (/en), set Hide in menus on /en – otherwise “English” shows up in the German menu. The page stays reachable through the language switcher. lang_root(12) returns the language tree of page 12.
Collections per language: A Collection has one language (schema → Language of the entries). For several languages you create separate Collections, e.g. blog (language de) and blog-en (en), and link the entries in the entry dialog under Language versions – just like pages; the search covers the entries of the other Collections with a language. Save new entries first. If the Collections have a detail page (schema), the detail pages also get hreflang and sitemap entries, and translations(post) returns the versions of an entry:
{% with post=item("blog", "slug", route.slug) %}
{% for v in translations(post) %}<a href="{{ v.url }}" hreflang="{{ v.lang }}">{{ language_name(v.lang) }}</a> {% endfor %}
{% endwith %}
Limits:
- IllusionCMS only remembers what belongs together. It does not translate anything and does not detect whether a version is outdated.
- Pages can only be linked with pages, entries only with entries. Entries from Collections without a language cannot be linked.
nav()always shows the website’s main menu (subpages of the home page); for menus per language usechildren(lang_root().id)(see above).- The language of the admin interface (at the bottom of the menu) has nothing to do with the language of the content.
Moving pages
In the page tree via drag & drop:
- drag onto the top edge of a page → sort in before it,
- onto the bottom edge → after it,
- onto the middle → as a subpage.
The home page cannot be moved, and a page cannot be moved into its own subtree. When you move a page, the URL of the page and all its subpages changes – links in the HTML of other pages are not adjusted automatically. For menus, it’s therefore better to use nav()/children() (see Templates), which return the current paths.
The page tree can be collapsed via the arrow in its header when you need more room for the editor.
Publishing and drafts
- Published – the page is publicly reachable.
- Draft – the page returns 404 publicly. A draft hides only itself; published subpages remain reachable. This lets you create “folders” such as
/blogthat are not a page themselves, while their articles are. - When signed in, you still see drafts: append
?preview=1to the URL (e.g./neue-seite?preview=1) or use the path link at the top of the editor. - Preview links for others (e.g. clients): in the Settings tab under Preview links → validity (1, 7 or 30 days) → Create link (it is copied right away). The link shows the saved state of the page and its subpages without signing in – even as a draft, and for Catch-All pages also Collection entries that are still drafts (
/blog/neuer-artikel?ic_preview=…; in the template,previewis then true). It does not open other pages. Search engines getnoindex, the statistics count nothing, nothing is cached. Expired or revoked links (trash can) show “Preview expired”. In remote mode, the link is created on the server and points to its address.
Page settings
In the editor’s Settings tab:
| Setting | Meaning |
|---|---|
| Title | Name of the page in the page tree, in menus (nav()) and default for <title>. |
| Slug | URL part. Cannot be changed for the home page. |
| Language | Language of the content as a code (de, en, fr, de-at …), inherited by subpages without their own. Empty = inherits from above; the hint shows the inherited one. See Multilingual websites. |
| Language versions | Link the same page in other languages – for hreflang, sitemap and language switchers. |
| SEO title | Optional <title>, evaluated as a template – e.g. with the title of the current blog article. Empty = title. |
| Meta description | <meta name="description">, template syntax allowed. |
| Preview image | Image for link previews in messengers and social networks (og:image), ideally 1200 × 630 px. Applies to all subpages without their own – so an image on the website (root page) is the default for all pages. |
Attributes for <html> / <body> |
Output unchanged, e.g. class="dark". Without lang=, IllusionCMS sets lang from the Language field; your own lang= here takes precedence. Webflow imports need data-wf-page/data-wf-site here. |
| Published | see above |
| Hide in menus | The page is missing from nav() and children() but stays published and reachable – for legal notice, privacy policy, thank-you pages, landing pages. With nav(true) or children(id, true) you still get it, e.g. for the footer menu. The page tree shows no menu. Not for home pages. |
| Do not list in the sitemap | The page does not appear in /sitemap.xml (thank-you pages, landing pages). |
| noindex | Search engines should not include the page in their index. Sets <meta name="robots" content="noindex"> and removes the page from the sitemap. |
| nofollow | Search engines should not follow the links on the page (<meta name="robots" content="nofollow">). Both together result in noindex, nofollow. |
| Disable cache | The page is rendered anew on every request (see Cache). |
| Catch-All | The page also answers non-existent subpaths – the basis for dynamic pages. |
Link previews (Open Graph): Every page automatically gets og:title (SEO title), og:description (meta description), og:url, og:site_name (name of the website), og:type and twitter:card – with a preview image, additionally og:image including size and alt text (from the file manager), then as a large card. The addresses are absolute: via the website’s first domain, or without a domain via the requested address. If a tag is already in the Head field or in the imported HTML (Webflow brings its own), it is not output twice – this is how you set a custom image for, e.g., blog articles of a Catch-All page:
{% if route.slug %}<meta property="og:image" content="https://www.firma.de{{ file(item("blog", "slug", route.slug).bild).url }}">{% endif %}
Archive as MHTML (at the bottom of the tab) downloads the saved state of the page as a single .mhtml file – with CSS, images, fonts and favicon, even from third-party servers (e.g. Google Fonts). Chrome and Edge open it with a double-click, even offline; Firefox and Safari cannot open MHTML. It is meant as a snapshot for archiving:
- No scripts: Browsers don’t run JavaScript in MHTML, so scripts aren’t packed in at all. Sliders, menus or content loaded via JavaScript appear in their script-less state.
- No srcset: Images come in the size from
src;srcsetand<picture><source>are removed. - Files over 25 MB (e.g. videos) and unreachable files are left out; the page then keeps pointing to the original address.
- Unsaved changes in the editor are not included – save first.
Custom 404 page
Each website can have its own error page. It is shown for all paths that aren’t found – with status 404 – and receives the requested path as route.path.
- Choose any page: In the editor of the home page under Settings → Website → 404 page. All pages of this website are available; the chosen page stays reachable under its own URL but is missing from the sitemap. Drafts are ignored.
- Automatic (default): a published page with the slug
404directly under the home page. - If there is neither – or no website at all yet –, the CMS shows a plain default page.
{{ not_found() }} in a template (e.g. on detail pages) returns the same 404 page.
Redirects
Under Redirects (main menu) you redirect addresses that no longer exist to existing ones – e.g. old URLs after moving from Webflow, or entries of a Catch-All page whose URL part you changed (/blog/123 → /blog/mein-artikel).
- Create: From (old address – a path, or simply paste the old URL), To (path or external URL; suggestions from the page tree), type 301 permanent (default, for search engines) or 302 temporary.
- Redirects apply only to dead addresses – if there is a page at the path, it is shown. Upper/lower case and a trailing slash don’t matter; query parameters (
?utm_source=…) are passed on to the target. - Automatic: If you rename or move a published page, IllusionCMS creates redirects from the old addresses of the page and all its subpages (marked auto). Existing redirects to the old address are updated along with it – no chains are created.
- Target missing marks redirects whose target is no longer a page.
- 404 log: Below, you see which dead addresses are being requested – how often, when last, and from where (e.g. an old link on Facebook). Redirect copies the address into the form. Ignore does not delete: it only hides the address, which moves with its counter to the Ignored tab and does not show up as open again on further requests. Many entries like
/wp-login.phpcome from bots and can be ignored. If you made a mistake, bring the address back with Restore in the Ignored tab; Ignore all and Restore all act on the whole list. Ignored addresses are not part of the CSV export. - Edit in Excel (CSV): Export CSV (top right) downloads the open addresses from the 404 log – most frequent first, column To empty – and below them all redirects as CSV. Enter the targets in To (a path like
/neuor a URL), change targets or Status (301/302) of existing redirects if needed, and upload the file again with Import CSV. Matching is done via From: rows with a target create or change redirects, rows without a target stay open. With Delete redirects that are missing from the file or have no target you can also clean up via the file. The preview shows every change and all errors (e.g. loops, duplicate paths, paths of existing pages) – if there are errors, nothing is applied. Excel: save as CSV UTF-8; the columns Type, Hits, Last and Referrer are for overview only. Cells starting with=,+,-or@get a leading'in the export (collections too) so Excel does not run them as formulas; the import removes it again. - In the static export the redirects are in the
.htaccess; in bundles they are transferred too.
robots.txt
Every website automatically has a /robots.txt that allows search engines everything and points to the sitemap:
User-agent: *
Allow: /
Sitemap: https://www.firma.de/sitemap.xml
You enter your own rules in the editor of the home page under Settings → Website → robots.txt, e.g. Disallow: /intern/. If a Sitemap: line is missing there, it is added. Individual pages are better removed from the index with noindex – a page blocked via robots.txt can still show up in search results, just without content.
Sitemap
At /sitemap.xml, IllusionCMS automatically generates a sitemap of all published pages – without the 404 page and without pages with Do not list in the sitemap or noindex. Subpages of drafts are included (after all, they are reachable). Detail pages behind Catch-All pages (individual blog articles) are added automatically if the page reads its entry via item("blog", "slug", route.slug) as in the Catch-All example: then every published entry with a non-empty slug is in the sitemap as /blog/<slug>, with the entry’s modification date.
Visitor statistics
Under Statistics you see how many visitors came – with several websites, for all of them together (default; the page list then shows the website before the path) or for a selected one –, which pages they viewed, where they came from (domain of the referring link or utm_source), from which countries (world map with list) and with which browser, operating system and device – for today, 7 or 30 days, 12 months or a custom period (From – to, three years at most), each compared with the previous period of the same length. For more than three months, the chart shows months instead of days.
How counting works: on the server, for every successfully delivered page view (including from the cache) – without cookies, without JavaScript, without any outside service. Not counted are bots and crawlers, preloaded pages (prefetch), the preview, and your own requests while you are signed in to the admin.
Unique visitors are recognized by a hash of IP address, browser identifier and a random value that is newly generated every day; the old one is deleted. IP addresses are never stored, and the next day the hash can no longer be attributed to anyone. As a result, someone who comes on three days counts as three daily visitors – the visitor count of a period is the sum of the days. Referrer, country, browser, operating system and device are counted per visitor and day, not per request.
Downloads: The Downloads card shows which file was downloaded how often – counted are requests under /media/… for files such as PDF, ZIP or Office documents, including via direct links (e.g. from a newsletter; the website follows from the domain). Images, fonts and CSS/JS never count, nor do files that a page embeds (e.g. a PDF in an <iframe>). For videos and PDFs that the browser loads in parts, only the first request counts. As with pages, bots, prefetch and your own requests as a signed-in admin are excluded; Visitors counts unique visitors per file and day. Downloads don’t count as page views. An aborted download still counts – it can’t be more precise without individual requests. There is no counting in the static export.
Countries: When counting, the server looks up the IP in the free allocation lists of the five Internet registries (RIPE NCC, ARIN, APNIC, LACNIC, AFRINIC) – locally, without any outside service; only the country code is stored. It is the registration country of the address block, usually the country of the Internet provider – with VPNs, corporate networks and some mobile carriers it differs from the actual location. Cities are deliberately not available. IllusionCMS downloads the lists (about 45 MB in total) right after the first start and then weekly in the nightly job; you can see their status under Settings → System → Country data. There you can update immediately, turn countries off entirely or – for servers without Internet access – upload the delegated-…-extended-latest files by hand. If a sync fails, the previous data remains. Visits before the first sync have no country.
Export CSV downloads all daily values of the selected period (and of the selected website, or all of them) – per day one row for the website as a whole, each page, each attribute (referrer, browser, operating system, device, country) and each downloaded file (type Download), with views or downloads and visitors. The file opens directly in Excel and is suitable for pivot tables; attributes count only visitors.
Only daily values are stored; they remain until you reset them at the bottom of the page. The statistics are not part of bundles. Whether this method is permissible for your website without consent depends on the individual case – it is designed for data minimization but does not replace legal advice; mention it in your privacy policy.