nibula 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.eleventy.js +81 -81
  2. package/.eleventyignore +3 -3
  3. package/.github/workflows/publish.yml +37 -0
  4. package/CHANGELOG.md +179 -148
  5. package/LICENSE +169 -169
  6. package/NOTICE +4 -4
  7. package/README.md +120 -123
  8. package/bin/create.js +507 -507
  9. package/bin/nibula.js +317 -305
  10. package/docs/Assistant CLI.md +65 -65
  11. package/docs/Backend.md +295 -295
  12. package/docs/Components.md +84 -84
  13. package/docs/Creating pages.md +64 -64
  14. package/docs/Deploy.md +189 -189
  15. package/docs/Head and SEO.md +138 -138
  16. package/docs/Javascript.md +50 -50
  17. package/docs/Styling with SCSS.md +141 -141
  18. package/nginx.conf +75 -75
  19. package/package.json +1 -1
  20. package/src/backend/.htaccess +6 -6
  21. package/src/backend/_core/composer.json +5 -5
  22. package/src/backend/_core/composer.lock +492 -492
  23. package/src/backend/_core/index.js +267 -267
  24. package/src/backend/_core/index.php +147 -147
  25. package/src/backend/_core/init.js +52 -52
  26. package/src/backend/_core/init.php +33 -33
  27. package/src/backend/_core/modules/RateLimiter.js +58 -58
  28. package/src/backend/_core/modules/RateLimiter.php +30 -30
  29. package/src/backend/_core/modules/Response.js +59 -59
  30. package/src/backend/_core/modules/Response.php +48 -48
  31. package/src/backend/api/protected/example-protected.js +23 -23
  32. package/src/backend/api/protected/example-protected.php +16 -16
  33. package/src/backend/api/public/example-public.js +24 -24
  34. package/src/backend/api/public/example-public.php +16 -16
  35. package/src/backend/backend-node.service.example +30 -30
  36. package/src/backend/database/Database.js +46 -46
  37. package/src/backend/database/Database.php +23 -23
  38. package/src/backend/example.config.js +37 -37
  39. package/src/backend/example.config.php +27 -27
  40. package/src/backend/package.json +18 -18
  41. package/src/backend/web.config +16 -16
  42. package/src/frontend/.htaccess +51 -51
  43. package/src/frontend/404.njk +17 -17
  44. package/src/frontend/assets/brand/favicon.svg +54 -54
  45. package/src/frontend/assets/brand/logo.svg +54 -54
  46. package/src/frontend/components/global/footer.njk +25 -25
  47. package/src/frontend/components/global/header.njk +6 -6
  48. package/src/frontend/components/welcome.njk +114 -114
  49. package/src/frontend/data/site.json +48 -48
  50. package/src/frontend/index.njk +8 -8
  51. package/src/frontend/js/modules/exampleModule.js +2 -2
  52. package/src/frontend/js/pages/404.js +6 -6
  53. package/src/frontend/js/pages/homepage.js +6 -6
  54. package/src/frontend/layouts/base.njk +132 -132
  55. package/src/frontend/layouts/page-components.njk +13 -13
  56. package/src/frontend/llms.njk +17 -17
  57. package/src/frontend/robots.njk +7 -7
  58. package/src/frontend/scss/modules/_animations.scss +24 -24
  59. package/src/frontend/scss/modules/_footer.scss +27 -27
  60. package/src/frontend/scss/modules/_global.scss +47 -47
  61. package/src/frontend/scss/modules/_header.scss +27 -27
  62. package/src/frontend/scss/modules/_mobile.scss +29 -29
  63. package/src/frontend/scss/modules/_root.scss +34 -34
  64. package/src/frontend/scss/modules/_typography.scss +14 -14
  65. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
  66. package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
  67. package/src/frontend/scss/modules/frameworks/_foundation.scss +138 -139
  68. package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
  69. package/src/frontend/scss/pages/404.scss +27 -27
  70. package/src/frontend/scss/pages/homepage.scss +22 -22
  71. package/src/frontend/sitemap.njk +17 -17
  72. package/src/frontend/ts/modules/exampleModule.ts +2 -2
  73. package/src/frontend/ts/pages/404.ts +6 -6
  74. package/src/frontend/ts/pages/homepage.ts +6 -6
  75. package/src/frontend/web.config +55 -55
  76. package/tools/config/messages.json +3 -1
  77. package/tools/res/templates/template.js +6 -6
  78. package/tools/res/templates/template.njk +8 -8
  79. package/tools/res/templates/template.scss +22 -22
  80. package/tools/res/templates/template.ts +6 -6
  81. package/tsconfig.json +24 -24
package/docs/Deploy.md CHANGED
@@ -1,190 +1,190 @@
1
- # Server Configuration
2
-
3
- Nibula builds a static site into your output folder (`out`). To put it online you
4
- upload that folder to a web server. Which server config you need depends on
5
- **where** you publish and **which backend** you chose at scaffold time (PHP or
6
- Node.js — see `docs/Backend.md`).
7
-
8
- The shipped `.htaccess`, `web.config` and `nginx.conf` are written to cover
9
- **both** backends, so you don't swap config files based on your choice. There is
10
- one caveat on Apache/IIS, explained at the end.
11
-
12
- ## Prerequisites
13
-
14
- **PHP backend:** a server with PHP support (Apache, Nginx + PHP-FPM, or IIS +
15
- FastCGI) and Composer for the backend dependencies. Works on typical shared
16
- hosting.
17
-
18
- **Node backend:** Node.js 18+ on the server and the ability to run a
19
- long-running process — i.e. a VPS you control, not typical shared hosting. No PHP
20
- or Composer required.
21
-
22
- ## Where to publish
23
-
24
- ### Shared hosting (Apache — e.g. Aruba, most cPanel providers)
25
-
26
- The most common and beginner-friendly option, and the natural home for the
27
- **PHP** backend (shared hosting can't keep a Node process alive). You rent space
28
- on a server that's already set up and just upload your files.
29
-
30
- Take the **contents of the `out` folder** and upload them to the hosting's public
31
- web root (often `htdocs`, `public_html`, or `www`). That's it — the site is live.
32
- `.htaccess` (Apache) or `web.config` (IIS) are already inside `out` and get picked
33
- up automatically, handling security and routing for you.
34
-
35
- ### Your own server (Nginx — e.g. an Ubuntu VPS)
36
-
37
- If you rent a bare server (a VPS) and install everything yourself, it typically
38
- runs **Nginx** on **Ubuntu Server**. Here nothing is pre-configured: you set up
39
- Nginx, the backend, and the security rules by hand. This is the setup for the
40
- **Node** backend (and also works for PHP + PHP-FPM). The Nginx section below
41
- gives you the essential rules to start from.
42
-
43
- ---
44
-
45
- ## Apache (`.htaccess`)
46
-
47
- `.htaccess` files at `src/frontend/` and `src/backend/` are copied into the build
48
- output by Eleventy. No setup required — upload `out` and Apache reads them.
49
-
50
- They cover:
51
- - Directory listing disabled
52
- - 403 / 404 → `/404.html`
53
- - Sensitive files blocked (`web.config`, dotfiles, etc.)
54
- - Backend source directory sealed — all access funnels through the front controller
55
- - `/api/*` routing (see the dual-backend note below)
56
-
57
- **Dual backend:** if `mod_proxy` is available, `/api` is reverse-proxied to the
58
- Node backend (`127.0.0.1:3000`); if not (typical shared hosting), `/api` is
59
- rewritten to the PHP front controller. To force PHP on a server that *has*
60
- `mod_proxy`, comment the `mod_proxy` block in `.htaccess`.
61
-
62
- ## IIS (`web.config`)
63
-
64
- For Windows-based hosting. `web.config` files at `src/frontend/` and
65
- `src/backend/` are copied into the build output by Eleventy and read
66
- automatically — same idea as `.htaccess`, different server.
67
-
68
- **Dual backend:** the `ApiToNode` rewrite rule (requires the ARR + URL Rewrite
69
- modules) reverse-proxies `/api` to Node and wins if present. To use PHP instead,
70
- remove the `ApiToNode` rule; the `ApiToPhp` fallback then rewrites `/api` to the
71
- PHP front controller.
72
-
73
- ## Nginx
74
-
75
- Used when you run your **own Ubuntu Server** (a VPS). Unlike Apache and IIS, Nginx
76
- does **not** read per-directory config files, so there's nothing automatic here.
77
-
78
- **`nginx.conf` in the project root is not a ready-to-use file.** It is a set of
79
- directives you adapt into a complete Nginx `server { }` block — the one that
80
- defines your domain (`server_name`), ports (`listen`), and SSL certificates.
81
- Paste these rules inside your own server block and set `root` to your `out`
82
- folder.
83
-
84
- They provide:
85
- - `server_tokens off` — hides the Nginx version
86
- - `autoindex off` — disables directory listing
87
- - 403 / 404 → `/404.html`
88
- - Backend source directory (`/backend/`) sealed with `return 404`
89
- - Server config files (`.htaccess`, `web.config`) blocked anywhere in the tree
90
- - `/api/*` routed to the backend (see below)
91
- - Security headers on static responses
92
-
93
- **Dual backend (auto-fallback):** the shipped `nginx.conf` sends `/api` to the
94
- Node backend on `127.0.0.1:3000` and, if Node is unreachable (502/504), falls
95
- back automatically to the PHP front controller. So the same config works whether
96
- you deployed Node or PHP — Nginx is the only server that can do this fallback
97
- automatically.
98
-
99
- > Setting up Nginx fully (SSL, HTTPS redirect, PHP-FPM and/or the Node service)
100
- > goes beyond these rules. These directives are the security starting point, not
101
- > a complete server setup.
102
-
103
- ### PHP-FPM socket
104
-
105
- When using the PHP path, the socket in the `/api` fallback targets Debian/Ubuntu
106
- by default. Check with `ls /run/php/` and adjust:
107
-
108
- | Distro | Path |
109
- |---|---|
110
- | Debian / Ubuntu | `unix:/run/php/php8.3-fpm.sock` |
111
- | RHEL / Fedora | `unix:/run/php-fpm/php-fpm.sock` |
112
- | TCP fallback | `127.0.0.1:9000` |
113
-
114
- ---
115
-
116
- ## Starting the Node backend on the server
117
-
118
- Unlike PHP, the Node backend is **not** executed by the web server per request —
119
- it is a process you start and keep alive. After uploading your built `out` folder:
120
-
121
- 1. Install the backend's runtime deps (only needed if an endpoint uses the DB):
122
- ```bash
123
- cd /var/www/your-site/out/backend
124
- npm install
125
- ```
126
-
127
- 2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js` to
128
- `config.js` if missing) and fill in your values.
129
-
130
- 3. Start it — quick test first:
131
- ```bash
132
- node _core/index.js
133
- # -> [backend-node] listening on http://127.0.0.1:3000 (env: production)
134
- ```
135
- Then, from the server, confirm it answers:
136
- ```bash
137
- curl http://127.0.0.1:3000/api/example-public
138
- ```
139
-
140
- > Running `node _core/index.js` directly ties the process to your SSH session —
141
- > close the terminal and the backend dies. The recommended way to try it out
142
- > without staying stuck there is **`screen`** (a detachable terminal):
143
- > ```bash
144
- > screen -S backend # open a named session
145
- > node _core/index.js # start the backend inside it
146
- > # press Ctrl+A then D to detach — the backend keeps running
147
- > # reattach later with: screen -r backend
148
- > ```
149
- > `screen` is perfect for testing and small setups. For a real production
150
- > deployment that also restarts on crash/reboot, prefer **systemd** (step 4).
151
-
152
- 4. To keep it running after logout, use **systemd**. An example unit ships at
153
- `backend/backend-node.service.example` — copy it to
154
- `/etc/systemd/system/backend-node.service`, adjust `WorkingDirectory` and the
155
- `node` path (`which node`), then:
156
- ```bash
157
- sudo systemctl daemon-reload
158
- sudo systemctl enable --now backend-node
159
- sudo systemctl status backend-node
160
- ```
161
- (`pm2` also works: `pm2 start _core/index.js --name backend-node`.)
162
-
163
- The web server then reverse-proxies `/api` to this process; with the shipped
164
- `nginx.conf`, if Node is down Nginx falls back to PHP automatically.
165
-
166
- ### Environment variables
167
-
168
- | Variable | Default | Purpose |
169
- |---|---|---|
170
- | `PORT` | `3000` | Port Node listens on (match your proxy target) |
171
- | `HOST` | `127.0.0.1` | Bind address (keep it local; the web server is the public face) |
172
- | `APP_ENV` | `production` | `production` hides error details; anything else = verbose 500s |
173
- | `DOCUMENT_ROOT` | auto (`out/`) | Where `404.html` lives; auto-detected as the folder above `backend/` |
174
-
175
- ---
176
-
177
- ## The one caveat on Apache and IIS
178
-
179
- Nginx can health-check the Node upstream and fall back to PHP automatically.
180
- Apache (`.htaccess`) and IIS (`web.config`) **cannot** — a per-directory config
181
- can't know whether Node is up. So there the routing is decided by configuration,
182
- not at runtime:
183
-
184
- - **Apache:** `mod_proxy` present → Node; absent → PHP. Comment the `mod_proxy`
185
- block to force PHP on a proxy-enabled server.
186
- - **IIS:** `ApiToNode` rule present (needs ARR) → Node; remove it to use the
187
- `ApiToPhp` fallback.
188
-
189
- Since a given deployment runs only one backend, this is a one-time setup, not a
1
+ # Server Configuration
2
+
3
+ Nibula builds a static site into your output folder (`out`). To put it online you
4
+ upload that folder to a web server. Which server config you need depends on
5
+ **where** you publish and **which backend** you chose at scaffold time (PHP or
6
+ Node.js — see `docs/Backend.md`).
7
+
8
+ The shipped `.htaccess`, `web.config` and `nginx.conf` are written to cover
9
+ **both** backends, so you don't swap config files based on your choice. There is
10
+ one caveat on Apache/IIS, explained at the end.
11
+
12
+ ## Prerequisites
13
+
14
+ **PHP backend:** a server with PHP support (Apache, Nginx + PHP-FPM, or IIS +
15
+ FastCGI) and Composer for the backend dependencies. Works on typical shared
16
+ hosting.
17
+
18
+ **Node backend:** Node.js 18+ on the server and the ability to run a
19
+ long-running process — i.e. a VPS you control, not typical shared hosting. No PHP
20
+ or Composer required.
21
+
22
+ ## Where to publish
23
+
24
+ ### Shared hosting (Apache — e.g. Aruba, most cPanel providers)
25
+
26
+ The most common and beginner-friendly option, and the natural home for the
27
+ **PHP** backend (shared hosting can't keep a Node process alive). You rent space
28
+ on a server that's already set up and just upload your files.
29
+
30
+ Take the **contents of the `out` folder** and upload them to the hosting's public
31
+ web root (often `htdocs`, `public_html`, or `www`). That's it — the site is live.
32
+ `.htaccess` (Apache) or `web.config` (IIS) are already inside `out` and get picked
33
+ up automatically, handling security and routing for you.
34
+
35
+ ### Your own server (Nginx — e.g. an Ubuntu VPS)
36
+
37
+ If you rent a bare server (a VPS) and install everything yourself, it typically
38
+ runs **Nginx** on **Ubuntu Server**. Here nothing is pre-configured: you set up
39
+ Nginx, the backend, and the security rules by hand. This is the setup for the
40
+ **Node** backend (and also works for PHP + PHP-FPM). The Nginx section below
41
+ gives you the essential rules to start from.
42
+
43
+ ---
44
+
45
+ ## Apache (`.htaccess`)
46
+
47
+ `.htaccess` files at `src/frontend/` and `src/backend/` are copied into the build
48
+ output by Eleventy. No setup required — upload `out` and Apache reads them.
49
+
50
+ They cover:
51
+ - Directory listing disabled
52
+ - 403 / 404 → `/404.html`
53
+ - Sensitive files blocked (`web.config`, dotfiles, etc.)
54
+ - Backend source directory sealed — all access funnels through the front controller
55
+ - `/api/*` routing (see the dual-backend note below)
56
+
57
+ **Dual backend:** if `mod_proxy` is available, `/api` is reverse-proxied to the
58
+ Node backend (`127.0.0.1:3000`); if not (typical shared hosting), `/api` is
59
+ rewritten to the PHP front controller. To force PHP on a server that *has*
60
+ `mod_proxy`, comment the `mod_proxy` block in `.htaccess`.
61
+
62
+ ## IIS (`web.config`)
63
+
64
+ For Windows-based hosting. `web.config` files at `src/frontend/` and
65
+ `src/backend/` are copied into the build output by Eleventy and read
66
+ automatically — same idea as `.htaccess`, different server.
67
+
68
+ **Dual backend:** the `ApiToNode` rewrite rule (requires the ARR + URL Rewrite
69
+ modules) reverse-proxies `/api` to Node and wins if present. To use PHP instead,
70
+ remove the `ApiToNode` rule; the `ApiToPhp` fallback then rewrites `/api` to the
71
+ PHP front controller.
72
+
73
+ ## Nginx
74
+
75
+ Used when you run your **own Ubuntu Server** (a VPS). Unlike Apache and IIS, Nginx
76
+ does **not** read per-directory config files, so there's nothing automatic here.
77
+
78
+ **`nginx.conf` in the project root is not a ready-to-use file.** It is a set of
79
+ directives you adapt into a complete Nginx `server { }` block — the one that
80
+ defines your domain (`server_name`), ports (`listen`), and SSL certificates.
81
+ Paste these rules inside your own server block and set `root` to your `out`
82
+ folder.
83
+
84
+ They provide:
85
+ - `server_tokens off` — hides the Nginx version
86
+ - `autoindex off` — disables directory listing
87
+ - 403 / 404 → `/404.html`
88
+ - Backend source directory (`/backend/`) sealed with `return 404`
89
+ - Server config files (`.htaccess`, `web.config`) blocked anywhere in the tree
90
+ - `/api/*` routed to the backend (see below)
91
+ - Security headers on static responses
92
+
93
+ **Dual backend (auto-fallback):** the shipped `nginx.conf` sends `/api` to the
94
+ Node backend on `127.0.0.1:3000` and, if Node is unreachable (502/504), falls
95
+ back automatically to the PHP front controller. So the same config works whether
96
+ you deployed Node or PHP — Nginx is the only server that can do this fallback
97
+ automatically.
98
+
99
+ > Setting up Nginx fully (SSL, HTTPS redirect, PHP-FPM and/or the Node service)
100
+ > goes beyond these rules. These directives are the security starting point, not
101
+ > a complete server setup.
102
+
103
+ ### PHP-FPM socket
104
+
105
+ When using the PHP path, the socket in the `/api` fallback targets Debian/Ubuntu
106
+ by default. Check with `ls /run/php/` and adjust:
107
+
108
+ | Distro | Path |
109
+ |---|---|
110
+ | Debian / Ubuntu | `unix:/run/php/php8.3-fpm.sock` |
111
+ | RHEL / Fedora | `unix:/run/php-fpm/php-fpm.sock` |
112
+ | TCP fallback | `127.0.0.1:9000` |
113
+
114
+ ---
115
+
116
+ ## Starting the Node backend on the server
117
+
118
+ Unlike PHP, the Node backend is **not** executed by the web server per request —
119
+ it is a process you start and keep alive. After uploading your built `out` folder:
120
+
121
+ 1. Install the backend's runtime deps (only needed if an endpoint uses the DB):
122
+ ```bash
123
+ cd /var/www/your-site/out/backend
124
+ npm install
125
+ ```
126
+
127
+ 2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js` to
128
+ `config.js` if missing) and fill in your values.
129
+
130
+ 3. Start it — quick test first:
131
+ ```bash
132
+ node _core/index.js
133
+ # -> [backend-node] listening on http://127.0.0.1:3000 (env: production)
134
+ ```
135
+ Then, from the server, confirm it answers:
136
+ ```bash
137
+ curl http://127.0.0.1:3000/api/example-public
138
+ ```
139
+
140
+ > Running `node _core/index.js` directly ties the process to your SSH session —
141
+ > close the terminal and the backend dies. The recommended way to try it out
142
+ > without staying stuck there is **`screen`** (a detachable terminal):
143
+ > ```bash
144
+ > screen -S backend # open a named session
145
+ > node _core/index.js # start the backend inside it
146
+ > # press Ctrl+A then D to detach — the backend keeps running
147
+ > # reattach later with: screen -r backend
148
+ > ```
149
+ > `screen` is perfect for testing and small setups. For a real production
150
+ > deployment that also restarts on crash/reboot, prefer **systemd** (step 4).
151
+
152
+ 4. To keep it running after logout, use **systemd**. An example unit ships at
153
+ `backend/backend-node.service.example` — copy it to
154
+ `/etc/systemd/system/backend-node.service`, adjust `WorkingDirectory` and the
155
+ `node` path (`which node`), then:
156
+ ```bash
157
+ sudo systemctl daemon-reload
158
+ sudo systemctl enable --now backend-node
159
+ sudo systemctl status backend-node
160
+ ```
161
+ (`pm2` also works: `pm2 start _core/index.js --name backend-node`.)
162
+
163
+ The web server then reverse-proxies `/api` to this process; with the shipped
164
+ `nginx.conf`, if Node is down Nginx falls back to PHP automatically.
165
+
166
+ ### Environment variables
167
+
168
+ | Variable | Default | Purpose |
169
+ |---|---|---|
170
+ | `PORT` | `3000` | Port Node listens on (match your proxy target) |
171
+ | `HOST` | `127.0.0.1` | Bind address (keep it local; the web server is the public face) |
172
+ | `APP_ENV` | `production` | `production` hides error details; anything else = verbose 500s |
173
+ | `DOCUMENT_ROOT` | auto (`out/`) | Where `404.html` lives; auto-detected as the folder above `backend/` |
174
+
175
+ ---
176
+
177
+ ## The one caveat on Apache and IIS
178
+
179
+ Nginx can health-check the Node upstream and fall back to PHP automatically.
180
+ Apache (`.htaccess`) and IIS (`web.config`) **cannot** — a per-directory config
181
+ can't know whether Node is up. So there the routing is decided by configuration,
182
+ not at runtime:
183
+
184
+ - **Apache:** `mod_proxy` present → Node; absent → PHP. Comment the `mod_proxy`
185
+ block to force PHP on a proxy-enabled server.
186
+ - **IIS:** `ApiToNode` rule present (needs ARR) → Node; remove it to use the
187
+ `ApiToPhp` fallback.
188
+
189
+ Since a given deployment runs only one backend, this is a one-time setup, not a
190
190
  per-request concern.
@@ -1,139 +1,139 @@
1
- # Head & SEO
2
-
3
- Global settings live in `src/frontend/data/site.json` and are available everywhere via `{{ site.* }}`.
4
-
5
- ## Global fields
6
-
7
- ```json
8
- {
9
- "site_name": "Site name",
10
- "title": "Site title",
11
- "description": "Site description",
12
- "keywords": "keyword1, keyword2, keyword3",
13
- "domain": "yoursite.com",
14
- "url": "https://yoursite.com",
15
- "lang": "en",
16
- "author": "Name and surname",
17
- "theme": "dark",
18
- "logo": "/assets/brand/logo.svg",
19
- "legal": {
20
- "privacy": "",
21
- "cookie": "",
22
- "cookieControls": "",
23
- "terms": "",
24
- "copyright": {
25
- "year": "2026",
26
- "text": "Copyright text"
27
- }
28
- }
29
- }
30
- ```
31
-
32
- | Field | Purpose |
33
- |---|---|
34
- | `site_name` | Brand name (used in meta tags and JSON-LD) |
35
- | `title` | Default page title — fallback when a page has no `seo.title` |
36
- | `description` | Default meta description — fallback for pages |
37
- | `keywords` | Default meta keywords — fallback for pages |
38
- | `domain` / `url` | Canonical URLs, `og:url`, JSON-LD |
39
- | `lang` | HTML `lang` attribute |
40
- | `author` | Meta author and JSON-LD author |
41
- | `theme` | Site color scheme (`light` / `dark`) — sets `data-theme`, `data-bs-theme` and `color-scheme` |
42
- | `theme_color` | Browser UI / PWA bar color — should match the chosen `theme` |
43
- | `logo` | Path to the logo, also used as social image |
44
- | `legal.privacy` / `cookie` / `cookieControls` / `terms` | Legal page URLs |
45
- | `legal.copyright.year` / `text` | Footer copyright |
46
-
47
- ## Per-page SEO and CDN
48
-
49
- Each page is keyed by its camelCase `title` from the front matter:
50
-
51
- ```json
52
- "pages": {
53
- "examplePage": {
54
- "seo": {
55
- "title": "Example Page",
56
- "description": "Description",
57
- "keywords": "",
58
- "noindex": false,
59
- "canonical": ""
60
- },
61
- "cdn": {
62
- "css": ["https://example.com/lib.min.css"],
63
- "js": ["https://example.com/lib.min.js"]
64
- }
65
- }
66
- }
67
- ```
68
-
69
- | Field | Purpose | If empty / absent |
70
- |---|---|---|
71
- | `seo.title` | Page title | Falls back to global `title` |
72
- | `seo.description` | Meta description | Falls back to global `description` |
73
- | `seo.keywords` | Meta keywords | Falls back to global `keywords` |
74
- | `seo.noindex` | If `true`, emits `<meta name="robots" content="noindex, follow">` and drops the page from the sitemap | Page is indexable |
75
- | `seo.canonical` | Absolute canonical URL override | Computed as `url + page.url` |
76
- | `cdn.css` / `cdn.js` | Extra per-page CDN links | No extra links loaded |
77
-
78
- ## Fallback logic
79
-
80
- Global values are defaults, not duplicates. A page value is used when present; otherwise the global one applies:
81
-
82
- ```njk
83
- {{ pageData.seo.title or title or site.title }}
84
- {{ pageData.seo.description or site.description }}
85
- {{ pageData.seo.keywords or site.keywords }}
86
- ```
87
-
88
- `noindex` and `canonical` have no global default: `noindex` defaults to indexable behavior, `canonical` is computed from the URL. They exist per-page only.
89
-
90
- ## Theme
91
-
92
- `theme` is a single fixed value that drives the whole site:
93
-
94
- ```njk
95
- <html data-theme="{{ site.theme }}" data-bs-theme="{{ site.theme }}">
96
- <meta name="color-scheme" content="{{ site.theme }}">
97
- <meta name="theme-color" content="{{ site.theme_color }}">
98
- ```
99
-
100
- | Attribute | Purpose |
101
- |---|---|
102
- | `data-theme` | Your own hook — target it in SCSS to define theme variables |
103
- | `data-bs-theme` | Bootstrap's color mode. Ignored (harmless) if you use another framework |
104
- | `color-scheme` | Standard CSS — makes scrollbars, form controls and autofill follow the theme |
105
- | `theme-color` | Color of the browser UI bar on mobile |
106
-
107
- Keep `theme` and `theme_color` consistent: a `dark` theme with a white `theme_color` gives a white browser bar over a dark page.
108
-
109
- See the SCSS docs for how to hook your variables to `data-theme`.
110
-
111
- ## Favicon
112
-
113
- The three favicon tags are hardcoded in `base.njk`. To change the icons, replace the files in `src/frontend/assets/brand/` keeping the same names:
114
-
115
- ```html
116
- <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
117
- <link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
118
- <link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
119
- ```
120
-
121
- | File | Purpose |
122
- |---|---|
123
- | `favicon.svg` | Modern browsers, scales to any size |
124
- | `favicon-32.png` | Fallback for older browsers and some crawlers |
125
- | `apple-touch-icon.png` | iOS home screen icon — 180×180, must be opaque (iOS renders transparency as black) |
126
-
127
- > ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of the page as the home screen icon.
128
-
129
- ## AI & SEO bots
130
-
131
- `llms.txt`, `robots.txt` and `sitemap.xml` are generated from `.njk` templates and are publicly reachable (they must not be blocked by the server config).
132
-
133
- | File | Purpose | Reachable at |
134
- |---|---|---|
135
- | `llms.njk` | Tells AI models what the site is about | `yoursite.com/llms.txt` |
136
- | `robots.njk` | Controls search-engine crawling | `yoursite.com/robots.txt` |
137
- | `sitemap.njk` | Lists indexable pages (skips drafts, 404, and `noindex`) | `yoursite.com/sitemap.xml` |
138
-
1
+ # Head & SEO
2
+
3
+ Global settings live in `src/frontend/data/site.json` and are available everywhere via `{{ site.* }}`.
4
+
5
+ ## Global fields
6
+
7
+ ```json
8
+ {
9
+ "site_name": "Site name",
10
+ "title": "Site title",
11
+ "description": "Site description",
12
+ "keywords": "keyword1, keyword2, keyword3",
13
+ "domain": "yoursite.com",
14
+ "url": "https://yoursite.com",
15
+ "lang": "en",
16
+ "author": "Name and surname",
17
+ "theme": "dark",
18
+ "logo": "/assets/brand/logo.svg",
19
+ "legal": {
20
+ "privacy": "",
21
+ "cookie": "",
22
+ "cookieControls": "",
23
+ "terms": "",
24
+ "copyright": {
25
+ "year": "2026",
26
+ "text": "Copyright text"
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ | Field | Purpose |
33
+ |---|---|
34
+ | `site_name` | Brand name (used in meta tags and JSON-LD) |
35
+ | `title` | Default page title — fallback when a page has no `seo.title` |
36
+ | `description` | Default meta description — fallback for pages |
37
+ | `keywords` | Default meta keywords — fallback for pages |
38
+ | `domain` / `url` | Canonical URLs, `og:url`, JSON-LD |
39
+ | `lang` | HTML `lang` attribute |
40
+ | `author` | Meta author and JSON-LD author |
41
+ | `theme` | Site color scheme (`light` / `dark`) — sets `data-theme`, `data-bs-theme` and `color-scheme` |
42
+ | `theme_color` | Browser UI / PWA bar color — should match the chosen `theme` |
43
+ | `logo` | Path to the logo, also used as social image |
44
+ | `legal.privacy` / `cookie` / `cookieControls` / `terms` | Legal page URLs |
45
+ | `legal.copyright.year` / `text` | Footer copyright |
46
+
47
+ ## Per-page SEO and CDN
48
+
49
+ Each page is keyed by its camelCase `title` from the front matter:
50
+
51
+ ```json
52
+ "pages": {
53
+ "examplePage": {
54
+ "seo": {
55
+ "title": "Example Page",
56
+ "description": "Description",
57
+ "keywords": "",
58
+ "noindex": false,
59
+ "canonical": ""
60
+ },
61
+ "cdn": {
62
+ "css": ["https://example.com/lib.min.css"],
63
+ "js": ["https://example.com/lib.min.js"]
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ | Field | Purpose | If empty / absent |
70
+ |---|---|---|
71
+ | `seo.title` | Page title | Falls back to global `title` |
72
+ | `seo.description` | Meta description | Falls back to global `description` |
73
+ | `seo.keywords` | Meta keywords | Falls back to global `keywords` |
74
+ | `seo.noindex` | If `true`, emits `<meta name="robots" content="noindex, follow">` and drops the page from the sitemap | Page is indexable |
75
+ | `seo.canonical` | Absolute canonical URL override | Computed as `url + page.url` |
76
+ | `cdn.css` / `cdn.js` | Extra per-page CDN links | No extra links loaded |
77
+
78
+ ## Fallback logic
79
+
80
+ Global values are defaults, not duplicates. A page value is used when present; otherwise the global one applies:
81
+
82
+ ```njk
83
+ {{ pageData.seo.title or title or site.title }}
84
+ {{ pageData.seo.description or site.description }}
85
+ {{ pageData.seo.keywords or site.keywords }}
86
+ ```
87
+
88
+ `noindex` and `canonical` have no global default: `noindex` defaults to indexable behavior, `canonical` is computed from the URL. They exist per-page only.
89
+
90
+ ## Theme
91
+
92
+ `theme` is a single fixed value that drives the whole site:
93
+
94
+ ```njk
95
+ <html data-theme="{{ site.theme }}" data-bs-theme="{{ site.theme }}">
96
+ <meta name="color-scheme" content="{{ site.theme }}">
97
+ <meta name="theme-color" content="{{ site.theme_color }}">
98
+ ```
99
+
100
+ | Attribute | Purpose |
101
+ |---|---|
102
+ | `data-theme` | Your own hook — target it in SCSS to define theme variables |
103
+ | `data-bs-theme` | Bootstrap's color mode. Ignored (harmless) if you use another framework |
104
+ | `color-scheme` | Standard CSS — makes scrollbars, form controls and autofill follow the theme |
105
+ | `theme-color` | Color of the browser UI bar on mobile |
106
+
107
+ Keep `theme` and `theme_color` consistent: a `dark` theme with a white `theme_color` gives a white browser bar over a dark page.
108
+
109
+ See the SCSS docs for how to hook your variables to `data-theme`.
110
+
111
+ ## Favicon
112
+
113
+ The three favicon tags are hardcoded in `base.njk`. To change the icons, replace the files in `src/frontend/assets/brand/` keeping the same names:
114
+
115
+ ```html
116
+ <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
117
+ <link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
118
+ <link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
119
+ ```
120
+
121
+ | File | Purpose |
122
+ |---|---|
123
+ | `favicon.svg` | Modern browsers, scales to any size |
124
+ | `favicon-32.png` | Fallback for older browsers and some crawlers |
125
+ | `apple-touch-icon.png` | iOS home screen icon — 180×180, must be opaque (iOS renders transparency as black) |
126
+
127
+ > ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of the page as the home screen icon.
128
+
129
+ ## AI & SEO bots
130
+
131
+ `llms.txt`, `robots.txt` and `sitemap.xml` are generated from `.njk` templates and are publicly reachable (they must not be blocked by the server config).
132
+
133
+ | File | Purpose | Reachable at |
134
+ |---|---|---|
135
+ | `llms.njk` | Tells AI models what the site is about | `yoursite.com/llms.txt` |
136
+ | `robots.njk` | Controls search-engine crawling | `yoursite.com/robots.txt` |
137
+ | `sitemap.njk` | Lists indexable pages (skips drafts, 404, and `noindex`) | `yoursite.com/sitemap.xml` |
138
+
139
139
  To customize, edit `src/frontend/llms.njk` or `src/frontend/robots.njk` directly.