nibula 2.0.2 → 2.1.0
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.
- package/.eleventy.js +68 -8
- package/CHANGELOG.md +79 -0
- package/bin/create.js +2 -25
- package/docs/Components.md +80 -42
- package/docs/Creating pages.md +3 -1
- package/docs/Deploy.md +185 -127
- package/docs/Head and SEO.md +98 -56
- package/docs/Project structure.md +3 -2
- package/package.json +3 -1
- package/src/frontend/layouts/base.njk +1 -1
- package/src/frontend/routes/404.njk +4 -3
- package/src/frontend/routes/index.njk +4 -3
- package/tools/assistant.js +21 -2
- package/tools/cli/prompt.js +99 -13
- package/tools/config/messages.json +1 -0
- package/tools/config/settings.json +10 -4
- package/tools/lib/files.js +5 -0
- package/tools/lib/layouts.js +15 -0
- package/tools/lib/pageActions.js +13 -5
- package/tools/lib/paths.js +1 -0
- package/tools/res/templates/template.njk +4 -3
package/docs/Deploy.md
CHANGED
|
@@ -1,221 +1,279 @@
|
|
|
1
1
|
# Deploy
|
|
2
2
|
|
|
3
|
-
Nibula builds
|
|
4
|
-
|
|
5
|
-
backend** you chose (see `docs/Backend.md`).
|
|
3
|
+
Nibula builds your finished site into a folder called `out`. Publishing means
|
|
4
|
+
copying that folder onto a web server so people can reach it.
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
How you do that depends on two things: **where** you are publishing, and **which
|
|
7
|
+
backend** you picked when you created the project (see `docs/Backend.md`).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Start here
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
no process to keep alive — the server starts it, it answers, it exits.
|
|
13
|
-
- **Node** **is** the server: a long-running process listening on
|
|
14
|
-
`127.0.0.1:3000`, which the web server reverse-proxies `/api` to. Something has
|
|
15
|
-
to start it and keep it alive across reboots and crashes.
|
|
11
|
+
If you don't know which case you are in, answer this first:
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
**Did you choose the PHP backend or the Node backend?**
|
|
14
|
+
|
|
15
|
+
- **PHP** → almost anywhere works, including cheap hosting plans. Jump to
|
|
16
|
+
*Shared hosting* or *Windows hosting*.
|
|
17
|
+
- **Node** → you need a server you control. Jump to *Your own server*.
|
|
18
|
+
- **No `/api` endpoints at all?** Your site is fully static. Anything works,
|
|
19
|
+
including free hosting like Netlify or GitHub Pages.
|
|
20
|
+
|
|
21
|
+
## Why the backend decides everything
|
|
22
|
+
|
|
23
|
+
The two backends run in completely different ways, and this is the one thing
|
|
24
|
+
worth understanding before you pick a host.
|
|
25
|
+
|
|
26
|
+
**PHP** runs **once per visit**. A request arrives, the web server starts PHP,
|
|
27
|
+
PHP answers, PHP exits. Nothing stays running, so there is nothing to look after.
|
|
28
|
+
|
|
29
|
+
**Node is itself a server**. It is a program that must stay running all the time,
|
|
30
|
+
listening in the background, while the web server forwards `/api` requests to it.
|
|
31
|
+
Somebody has to start that program and restart it if the machine reboots or the
|
|
32
|
+
program crashes.
|
|
33
|
+
|
|
34
|
+
That is why **cheap shared hosting cannot run the Node backend**. On plans like
|
|
35
|
+
Aruba or cPanel you upload files into a folder and that's all you can do — there
|
|
36
|
+
is no way to start a program and keep it alive. If that's your host, choose PHP.
|
|
37
|
+
It isn't a limitation of Nibula.
|
|
38
|
+
|
|
39
|
+
> **Shared hosting** means a plan where you share a server with other customers
|
|
40
|
+
> and only get a folder to upload into. **A VPS** is a whole machine that is
|
|
41
|
+
> yours, where you can install and run whatever you want.
|
|
21
42
|
|
|
22
43
|
## Where you can publish
|
|
23
44
|
|
|
24
45
|
| Target | Frontend | PHP backend | Node backend |
|
|
25
46
|
|---|---|---|---|
|
|
26
|
-
| Shared hosting (Apache — Aruba, cPanel) | ✅ | ✅ | ❌ no
|
|
27
|
-
| Windows hosting (IIS) | ✅ | ✅
|
|
47
|
+
| Shared hosting (Apache — Aruba, cPanel) | ✅ | ✅ | ❌ no program can stay running |
|
|
48
|
+
| Windows hosting (IIS) | ✅ | ✅ | ⚠️ only with the ARR module |
|
|
28
49
|
| Your own VPS (Nginx on Ubuntu) | ✅ | ✅ | ✅ |
|
|
29
50
|
| Static hosting (Netlify, Vercel, GitHub Pages) | ✅ | ❌ | ❌ |
|
|
30
51
|
|
|
31
|
-
Static hosting serves files and nothing else
|
|
32
|
-
endpoints, unusable
|
|
52
|
+
Static hosting serves files and nothing else. Perfect if your site has no `/api`
|
|
53
|
+
endpoints, unusable if it does.
|
|
33
54
|
|
|
34
|
-
##
|
|
55
|
+
## What you need
|
|
35
56
|
|
|
36
|
-
**PHP backend:** a
|
|
57
|
+
**PHP backend:** a host with PHP support, and Composer to install the backend's
|
|
37
58
|
dependencies.
|
|
38
59
|
|
|
39
|
-
**Node backend:** Node.js 18
|
|
40
|
-
|
|
60
|
+
**Node backend:** Node.js 18 or newer, and command-line access to the server so
|
|
61
|
+
you can start the backend program.
|
|
41
62
|
|
|
42
63
|
## Editing the server config
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
a
|
|
46
|
-
|
|
65
|
+
Every server needs a configuration file telling it how to behave — which page to
|
|
66
|
+
show on a 404, which files to keep private, where to send `/api` requests. Nibula
|
|
67
|
+
writes these for you.
|
|
68
|
+
|
|
69
|
+
`.htaccess` (Apache) and `web.config` (IIS) are already inside `out`, ready to
|
|
70
|
+
upload. To change a rule, edit the original in `src/frontend/hosting/` — anything
|
|
71
|
+
you change directly inside `out` is erased the next time you build.
|
|
47
72
|
|
|
48
|
-
`nginx.conf`
|
|
49
|
-
|
|
50
|
-
server
|
|
73
|
+
`nginx.conf` is different. It sits in your project root and is **not** copied into
|
|
74
|
+
`out`, because Nginx does not read config files from the site folder. You install
|
|
75
|
+
it on the server yourself, as described below.
|
|
51
76
|
|
|
52
77
|
---
|
|
53
78
|
|
|
54
79
|
## Shared hosting (Apache)
|
|
55
80
|
|
|
56
|
-
Upload the
|
|
57
|
-
|
|
58
|
-
automatically: directory listing off, 403/404 → `/404.html`, sensitive files
|
|
59
|
-
blocked, backend source sealed, `/api/*` routed.
|
|
81
|
+
Upload **the contents of `out`** — not the folder itself — into your web root.
|
|
82
|
+
Depending on the host it is called `htdocs`, `public_html` or `www`.
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
That's it. `.htaccess` is already in there and Apache reads it automatically:
|
|
85
|
+
folder listings are off, errors show your `/404.html`, sensitive files are
|
|
86
|
+
blocked, the backend source is sealed off, and `/api/*` is routed.
|
|
87
|
+
|
|
88
|
+
**How `/api` gets routed:** if the server has a module called `mod_proxy`, `/api`
|
|
89
|
+
is forwarded to Node. If it doesn't — the usual case on shared hosting — `/api`
|
|
90
|
+
goes to PHP instead. To force PHP on a server that does have `mod_proxy`, comment
|
|
91
|
+
out the `mod_proxy` block in `.htaccess`.
|
|
65
92
|
|
|
66
93
|
## Windows hosting (IIS)
|
|
67
94
|
|
|
68
|
-
Same
|
|
69
|
-
automatically.
|
|
95
|
+
Same as above: upload the contents of `out`. `web.config` is already there and
|
|
96
|
+
read automatically.
|
|
70
97
|
|
|
71
|
-
**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
**How `/api` gets routed:** to PHP, by default. To use Node instead, uncomment
|
|
99
|
+
the `ApiToNode` rule in `web.config` and delete `ApiToPhp`. This only works if
|
|
100
|
+
the server has the ARR and URL Rewrite modules with proxying turned on — which
|
|
101
|
+
means a server you control, not shared hosting.
|
|
75
102
|
|
|
76
|
-
> ⚠️ **If you chose
|
|
77
|
-
>
|
|
78
|
-
> site works
|
|
79
|
-
> `/api` request returns an empty 404 instead
|
|
80
|
-
> front controller
|
|
103
|
+
> ⚠️ **If you chose Node and your host has no ARR** (Aruba and most Windows
|
|
104
|
+
> shared hosting), do **not** upload the `backend` folder. Without it the static
|
|
105
|
+
> site works fine and `/api/*` simply shows your `404.html`. Upload it and every
|
|
106
|
+
> `/api` request returns an empty 404 instead, because IIS sends the request to
|
|
107
|
+
> the PHP front controller — a file a Node project doesn't have.
|
|
81
108
|
|
|
82
109
|
---
|
|
83
110
|
|
|
84
111
|
## Your own server (Nginx on a VPS)
|
|
85
112
|
|
|
86
|
-
Nginx doesn't read
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
Nginx doesn't read config files from the site folder: the whole site is described
|
|
114
|
+
in one file kept elsewhere on the machine. Nibula ships that file, ready to use,
|
|
115
|
+
as `nginx.conf` in your project root.
|
|
116
|
+
|
|
117
|
+
It **handles both backends with no edits**: `/api` goes to Node, and if Node
|
|
118
|
+
isn't running the request falls through to PHP automatically.
|
|
119
|
+
|
|
120
|
+
Upload the contents of `out` to `/var/www/SITE_FOLDER`, then work through the
|
|
121
|
+
steps below.
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
below.
|
|
123
|
+
### 1. Fill in the placeholders
|
|
93
124
|
|
|
94
|
-
|
|
125
|
+
Open `nginx.conf` on your computer and replace these four words with your own
|
|
126
|
+
values:
|
|
95
127
|
|
|
96
|
-
| Placeholder |
|
|
128
|
+
| Placeholder | What to put there |
|
|
97
129
|
|---|---|
|
|
98
|
-
| `YOUR_DOMAIN` |
|
|
99
|
-
| `SITE_FOLDER` |
|
|
100
|
-
| `YOUR_CERTIFICATE` |
|
|
101
|
-
| `SITE_NAME` | file name in the commands below |
|
|
130
|
+
| `YOUR_DOMAIN` | your domain — appears twice, once for port 80 and once for 443 |
|
|
131
|
+
| `SITE_FOLDER` | the folder name you used under `/var/www/` |
|
|
132
|
+
| `YOUR_CERTIFICATE` | the folder name under `/etc/letsencrypt/live/` |
|
|
133
|
+
| `SITE_NAME` | the file name you'll use in the commands below — pick anything |
|
|
102
134
|
|
|
103
|
-
### 2. Install
|
|
135
|
+
### 2. Install what's missing
|
|
136
|
+
|
|
137
|
+
Skip anything already installed.
|
|
104
138
|
|
|
105
139
|
```bash
|
|
106
140
|
sudo apt update
|
|
107
141
|
sudo apt install -y nginx certbot python3-certbot-nginx
|
|
108
142
|
```
|
|
109
143
|
|
|
110
|
-
|
|
144
|
+
With the PHP backend, also:
|
|
111
145
|
|
|
112
146
|
```bash
|
|
113
147
|
sudo apt install -y php-fpm
|
|
114
148
|
```
|
|
115
149
|
|
|
116
|
-
### 3.
|
|
150
|
+
### 3. Get an HTTPS certificate
|
|
151
|
+
|
|
152
|
+
This is what makes your site load over `https://` instead of `http://`.
|
|
153
|
+
Certificates are free and last 90 days; the second command renews them
|
|
154
|
+
automatically.
|
|
117
155
|
|
|
118
156
|
```bash
|
|
119
157
|
sudo certbot certonly --nginx --cert-name SITE_NAME -d YOUR_DOMAIN
|
|
120
|
-
sudo systemctl enable --now certbot.timer
|
|
158
|
+
sudo systemctl enable --now certbot.timer
|
|
121
159
|
```
|
|
122
160
|
|
|
123
|
-
### 4. Install the site
|
|
161
|
+
### 4. Install the site file
|
|
124
162
|
|
|
125
163
|
```bash
|
|
126
|
-
#
|
|
164
|
+
# Go to the folder where Nginx keeps site files
|
|
127
165
|
cd /etc/nginx/sites-available
|
|
128
166
|
|
|
129
|
-
# Create and
|
|
167
|
+
# Create the file and paste in the contents of your edited nginx.conf
|
|
130
168
|
sudo nano SITE_NAME
|
|
131
|
-
# CTRL + O, ENTER, CTRL + X
|
|
169
|
+
# Paste, then: CTRL + O, ENTER, CTRL + X
|
|
132
170
|
|
|
133
|
-
#
|
|
171
|
+
# Switch the site on by copying it into sites-enabled
|
|
134
172
|
sudo cp SITE_NAME ../sites-enabled/
|
|
135
173
|
|
|
136
|
-
#
|
|
174
|
+
# Check the config for mistakes, then apply it
|
|
137
175
|
sudo nginx -t && sudo systemctl reload nginx
|
|
138
176
|
```
|
|
139
177
|
|
|
140
|
-
|
|
178
|
+
If `nginx -t` reports an error, fix it before reloading — Nginx keeps running on
|
|
179
|
+
the old config until the reload succeeds, so your site stays up.
|
|
141
180
|
|
|
142
|
-
|
|
181
|
+
### If something goes wrong
|
|
182
|
+
|
|
183
|
+
| What you see | What it usually means |
|
|
143
184
|
|---|---|
|
|
144
|
-
| `cannot load certificate ... Permission denied` | `nginx -t`
|
|
185
|
+
| `cannot load certificate ... Permission denied` | you ran `nginx -t` without `sudo` |
|
|
145
186
|
| `cannot load certificate ... No such file` | wrong certificate name |
|
|
146
|
-
| `no alternative certificate subject name` | certificate doesn't cover domain |
|
|
147
|
-
| `unknown directive "http2"` |
|
|
148
|
-
| NXDOMAIN
|
|
149
|
-
| 500 instead of
|
|
150
|
-
| 502 on `/api`, Node
|
|
151
|
-
| 502 on `/api`, PHP
|
|
187
|
+
| `no alternative certificate subject name` | the certificate doesn't cover this domain |
|
|
188
|
+
| `unknown directive "http2"` | Nginx older than 1.25 — check with `nginx -v` |
|
|
189
|
+
| NXDOMAIN or a timeout during certbot | your domain's DNS isn't pointing at this server yet |
|
|
190
|
+
| A blank 500 instead of your 404 page | `404.html` is missing from the upload |
|
|
191
|
+
| 502 on `/api`, Node backend | the Node program isn't running |
|
|
192
|
+
| 502 on `/api`, PHP backend | wrong php-fpm socket path in the config |
|
|
152
193
|
|
|
153
194
|
---
|
|
154
195
|
|
|
155
196
|
## Starting the Node backend on the server
|
|
156
197
|
|
|
157
|
-
After uploading `out`:
|
|
198
|
+
Only for the Node backend. After uploading `out`:
|
|
199
|
+
|
|
200
|
+
**1. Install its dependencies**
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
cd /var/www/SITE_FOLDER/backend
|
|
204
|
+
npm install
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**2. Create its config file**
|
|
158
208
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
cd /var/www/SITE_FOLDER/backend
|
|
162
|
-
npm install
|
|
163
|
-
```
|
|
209
|
+
Make sure `config.js` exists in `out/backend/`. If it doesn't, copy
|
|
210
|
+
`example.config.js` to `config.js` and fill in your values.
|
|
164
211
|
|
|
165
|
-
|
|
166
|
-
missing) and fill in your values.
|
|
212
|
+
**3. Try it out first**
|
|
167
213
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
screen -S node-backend
|
|
171
|
-
# In the new screen terminal:
|
|
172
|
-
sudo node /var/www/SITE_FOLDER/backend/_core/index.js
|
|
173
|
-
curl http://127.0.0.1:3000/api/example-public
|
|
174
|
-
```
|
|
214
|
+
`screen` lets you start a program, walk away, and come back to it later. Use it
|
|
215
|
+
to check the backend actually works before setting it up properly.
|
|
175
216
|
|
|
176
|
-
|
|
177
|
-
|
|
217
|
+
```bash
|
|
218
|
+
screen -S node-backend
|
|
219
|
+
# Inside the new screen:
|
|
220
|
+
sudo node /var/www/SITE_FOLDER/backend/_core/index.js
|
|
221
|
+
curl http://127.0.0.1:3000/api/example-public
|
|
222
|
+
```
|
|
178
223
|
|
|
179
|
-
|
|
180
|
-
|
|
224
|
+
If `curl` prints a JSON response, the backend is alive. Press `CTRL + A` then `D`
|
|
225
|
+
to leave the screen without stopping the program; `screen -r node-backend` brings
|
|
226
|
+
you back.
|
|
181
227
|
|
|
182
|
-
|
|
183
|
-
[Unit]
|
|
184
|
-
Description=Nibula Node backend
|
|
185
|
-
After=network.target
|
|
228
|
+
**4. Then set it up for real**
|
|
186
229
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
WorkingDirectory=/var/www/SITE_FOLDER/backend
|
|
191
|
-
ExecStart=/usr/bin/node /var/www/SITE_FOLDER/backend/_core/index.js
|
|
192
|
-
Restart=always
|
|
193
|
-
RestartSec=5
|
|
230
|
+
`screen` is for testing — the program dies if the server reboots. For a live site
|
|
231
|
+
use **systemd**, the part of Linux that manages background programs. It starts
|
|
232
|
+
your backend at boot and restarts it if it crashes.
|
|
194
233
|
|
|
195
|
-
|
|
196
|
-
WantedBy=multi-user.target
|
|
197
|
-
```
|
|
234
|
+
Create `/etc/systemd/system/backend-node.service`:
|
|
198
235
|
|
|
199
|
-
|
|
236
|
+
```ini
|
|
237
|
+
[Unit]
|
|
238
|
+
Description=Nibula Node backend
|
|
239
|
+
After=network.target
|
|
240
|
+
|
|
241
|
+
[Service]
|
|
242
|
+
Type=simple
|
|
243
|
+
User=www-data
|
|
244
|
+
WorkingDirectory=/var/www/SITE_FOLDER/backend
|
|
245
|
+
ExecStart=/usr/bin/node /var/www/SITE_FOLDER/backend/_core/index.js
|
|
246
|
+
Restart=always
|
|
247
|
+
RestartSec=5
|
|
248
|
+
|
|
249
|
+
[Install]
|
|
250
|
+
WantedBy=multi-user.target
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Adjust `WorkingDirectory`, and check the path to `node` with `which node`. Then:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
sudo systemctl daemon-reload
|
|
257
|
+
sudo systemctl enable --now backend-node
|
|
258
|
+
sudo systemctl status backend-node
|
|
259
|
+
```
|
|
200
260
|
|
|
201
|
-
|
|
202
|
-
sudo systemctl daemon-reload
|
|
203
|
-
sudo systemctl enable --now backend-node
|
|
204
|
-
sudo systemctl status backend-node
|
|
205
|
-
```
|
|
261
|
+
The last command should say `active (running)`.
|
|
206
262
|
|
|
207
|
-
|
|
263
|
+
(If you already use `pm2`, that works too:
|
|
264
|
+
`pm2 start _core/index.js --name backend-node`.)
|
|
208
265
|
|
|
209
|
-
###
|
|
266
|
+
### Settings the Node backend reads at startup
|
|
210
267
|
|
|
211
|
-
|
|
212
|
-
`Environment=KEY=value`.
|
|
268
|
+
These are set in the systemd file, on their own lines, as
|
|
269
|
+
`Environment=KEY=value`. All three have sensible defaults — you usually don't
|
|
270
|
+
need to touch them.
|
|
213
271
|
|
|
214
|
-
|
|
|
272
|
+
| Setting | Default | What it does |
|
|
215
273
|
|---|---|---|
|
|
216
|
-
| `PORT` | `3000` |
|
|
217
|
-
| `HOST` | `127.0.0.1` |
|
|
218
|
-
| `DOCUMENT_ROOT` |
|
|
274
|
+
| `PORT` | `3000` | the port Node listens on — must match the one in `nginx.conf` |
|
|
275
|
+
| `HOST` | `127.0.0.1` | keep this as-is: it means "local only", so Node is reachable through the web server and not directly from the internet |
|
|
276
|
+
| `DOCUMENT_ROOT` | detected automatically | where `404.html` lives — the folder above `backend/` |
|
|
219
277
|
|
|
220
|
-
Error
|
|
221
|
-
`config.
|
|
278
|
+
> Error detail is **not** set here. That's the `APP_ENV` key inside `config.js` /
|
|
279
|
+
> `config.php` — see `docs/Backend.md`.
|
package/docs/Head and SEO.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# Head & SEO
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `<head>` of an HTML page holds the information *about* the page rather than
|
|
4
|
+
its content: the title shown in the browser tab, the description search engines
|
|
5
|
+
display, the image that appears when someone shares the link. Nibula writes all
|
|
6
|
+
of it for you — you just fill in the values.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
There are two files to fill in:
|
|
9
|
+
|
|
10
|
+
- **`src/frontend/data/site.json`** — settings for the whole site
|
|
11
|
+
- **`src/frontend/data/pages.json`** — settings for one page at a time
|
|
12
|
+
|
|
13
|
+
A page uses its own value when it has one, and falls back to the site-wide value
|
|
14
|
+
when it doesn't. So filling in `site.json` well means most pages need nothing.
|
|
15
|
+
|
|
16
|
+
## Site-wide settings
|
|
17
|
+
|
|
18
|
+
Everything in `site.json` is available anywhere in your templates as
|
|
19
|
+
`{{ site.something }}`.
|
|
6
20
|
|
|
7
21
|
```json
|
|
8
22
|
{
|
|
@@ -29,24 +43,26 @@ Global settings live in `src/frontend/data/site.json` and are available everywhe
|
|
|
29
43
|
}
|
|
30
44
|
```
|
|
31
45
|
|
|
32
|
-
| Field |
|
|
46
|
+
| Field | What it's for |
|
|
33
47
|
|---|---|
|
|
34
|
-
| `site_name` |
|
|
35
|
-
| `title` | Default page title —
|
|
36
|
-
| `description` | Default
|
|
37
|
-
| `keywords` | Default
|
|
38
|
-
| `domain` / `url` |
|
|
39
|
-
| `lang` |
|
|
40
|
-
| `author` |
|
|
41
|
-
| `theme` |
|
|
42
|
-
| `theme_color` |
|
|
43
|
-
| `logo` | Path to
|
|
44
|
-
| `legal.privacy` / `cookie` / `terms` |
|
|
45
|
-
| `legal.copyright.year` / `text` |
|
|
46
|
-
|
|
47
|
-
## Per-page
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
| `site_name` | Your brand or project name |
|
|
49
|
+
| `title` | Default page title — used by any page that doesn't set its own |
|
|
50
|
+
| `description` | Default description — same idea |
|
|
51
|
+
| `keywords` | Default keywords — same idea |
|
|
52
|
+
| `domain` / `url` | Used to build full page addresses for search engines and social previews |
|
|
53
|
+
| `lang` | The language of your site (`en`, `it`, ...) |
|
|
54
|
+
| `author` | Your name |
|
|
55
|
+
| `theme` | Colour scheme of the site: `light` or `dark` |
|
|
56
|
+
| `theme_color` | Colour of the browser bar on phones — should match your theme |
|
|
57
|
+
| `logo` | Path to your logo, also used as the image in social previews |
|
|
58
|
+
| `legal.privacy` / `cookie` / `terms` | Addresses of your legal pages — the footer links to them |
|
|
59
|
+
| `legal.copyright.year` / `text` | The copyright line in the footer |
|
|
60
|
+
|
|
61
|
+
## Per-page settings
|
|
62
|
+
|
|
63
|
+
`pages.json` holds one entry per page, and the assistant creates it for you when
|
|
64
|
+
you add a page. Each entry is keyed by the page's `title` from its front matter —
|
|
65
|
+
the camelCase one.
|
|
50
66
|
|
|
51
67
|
```json
|
|
52
68
|
{
|
|
@@ -66,20 +82,23 @@ Per-page settings live in `src/frontend/data/pages.json`, available via `{{ page
|
|
|
66
82
|
}
|
|
67
83
|
```
|
|
68
84
|
|
|
69
|
-
| Field |
|
|
85
|
+
| Field | What it's for | If you leave it empty |
|
|
70
86
|
|---|---|---|
|
|
71
|
-
| `seo.title` |
|
|
72
|
-
| `seo.description` |
|
|
73
|
-
| `seo.keywords` |
|
|
74
|
-
| `seo.noindex` |
|
|
75
|
-
| `seo.canonical` |
|
|
76
|
-
| `cdn.css` / `cdn.js` | Extra
|
|
87
|
+
| `seo.title` | The page's title, shown in the browser tab and in search results | Uses the site-wide `title` |
|
|
88
|
+
| `seo.description` | The short summary search engines show under the title | Uses the site-wide `description` |
|
|
89
|
+
| `seo.keywords` | Keywords for this page | Uses the site-wide `keywords` |
|
|
90
|
+
| `seo.noindex` | Set to `true` to ask search engines **not** to list this page | The page can be found normally |
|
|
91
|
+
| `seo.canonical` | The one official address of this page, if the same content is reachable at more than one URL | Worked out automatically |
|
|
92
|
+
| `cdn.css` / `cdn.js` | Extra stylesheets or scripts loaded from the internet, on this page only | Nothing extra is loaded |
|
|
77
93
|
|
|
78
|
-
The assistant
|
|
94
|
+
> ⚠️ The assistant fills new entries with placeholder text like `"Description"`.
|
|
95
|
+
> Replace it — a page left that way tells search engines and AI crawlers that its
|
|
96
|
+
> description is literally "Description".
|
|
79
97
|
|
|
80
|
-
|
|
98
|
+
### How the fallback works
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
Site-wide values are defaults, not duplicates. You don't need to repeat them per
|
|
101
|
+
page. This is what the template actually does:
|
|
83
102
|
|
|
84
103
|
```njk
|
|
85
104
|
{{ pageData.seo.title or title or site.title }}
|
|
@@ -87,11 +106,15 @@ Global values are defaults, not duplicates. A page value is used when present; o
|
|
|
87
106
|
{{ pageData.seo.keywords or site.keywords }}
|
|
88
107
|
```
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
Read it as: use the page's value; if there isn't one, use the site's.
|
|
110
|
+
|
|
111
|
+
`noindex` and `canonical` work per-page only — there's no site-wide version,
|
|
112
|
+
because "hide the whole site from search" and "one address for the whole site"
|
|
113
|
+
aren't things you'd want.
|
|
91
114
|
|
|
92
115
|
## Theme
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
One value in `site.json` controls the colour scheme everywhere:
|
|
95
118
|
|
|
96
119
|
```njk
|
|
97
120
|
<html data-theme="{{ site.theme }}" data-bs-theme="{{ site.theme }}">
|
|
@@ -99,20 +122,24 @@ Global values are defaults, not duplicates. A page value is used when present; o
|
|
|
99
122
|
<meta name="theme-color" content="{{ site.theme_color }}">
|
|
100
123
|
```
|
|
101
124
|
|
|
102
|
-
|
|
|
125
|
+
| What it sets | Why it's there |
|
|
103
126
|
|---|---|
|
|
104
|
-
| `data-theme` | Your own hook — target it in SCSS to define theme
|
|
105
|
-
| `data-bs-theme` | Bootstrap's
|
|
106
|
-
| `color-scheme` | Standard CSS — makes scrollbars, form
|
|
107
|
-
| `theme-color` |
|
|
127
|
+
| `data-theme` | Your own hook — target it in your SCSS to define theme colours |
|
|
128
|
+
| `data-bs-theme` | Bootstrap's dark mode switch. Harmlessly ignored with other frameworks |
|
|
129
|
+
| `color-scheme` | Standard CSS — makes scrollbars, form fields and autofill match your theme |
|
|
130
|
+
| `theme-color` | The colour of the browser bar on phones |
|
|
108
131
|
|
|
109
|
-
Keep `theme` and `theme_color`
|
|
132
|
+
Keep `theme` and `theme_color` in agreement: a dark theme with a white
|
|
133
|
+
`theme_color` gives a white browser bar sitting on top of a dark page.
|
|
110
134
|
|
|
111
|
-
See the SCSS
|
|
135
|
+
See the **Styling with SCSS** DOC file for how to hook your own colours to
|
|
136
|
+
`data-theme`.
|
|
112
137
|
|
|
113
138
|
## Favicon
|
|
114
139
|
|
|
115
|
-
The
|
|
140
|
+
The favicon is the small icon in the browser tab. The three tags are written
|
|
141
|
+
directly in `base.njk`. To change the icons, replace the files in
|
|
142
|
+
`src/frontend/assets/brand/` **keeping the same names**:
|
|
116
143
|
|
|
117
144
|
```html
|
|
118
145
|
<link rel="icon" type="image/svg+xml" href="{{ '/assets/brand/favicon.svg' | url }}">
|
|
@@ -120,37 +147,52 @@ The three favicon tags are hardcoded in `base.njk`. To change the icons, replace
|
|
|
120
147
|
<link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/brand/apple-touch-icon.png' | url }}">
|
|
121
148
|
```
|
|
122
149
|
|
|
123
|
-
| File |
|
|
150
|
+
| File | What uses it |
|
|
124
151
|
|---|---|
|
|
125
|
-
| `favicon.svg` | Modern browsers
|
|
126
|
-
| `favicon-32.png` |
|
|
127
|
-
| `apple-touch-icon.png` | iOS home screen
|
|
152
|
+
| `favicon.svg` | Modern browsers — scales to any size |
|
|
153
|
+
| `favicon-32.png` | Older browsers and some crawlers |
|
|
154
|
+
| `apple-touch-icon.png` | The icon iOS uses when someone saves your site to their home screen. Must be 180×180 and **not** transparent — iOS turns transparency into black |
|
|
128
155
|
|
|
129
|
-
> ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of
|
|
156
|
+
> ⚠️ Without `apple-touch-icon.png`, iOS uses a screenshot of your page as the
|
|
157
|
+
> home screen icon instead.
|
|
130
158
|
|
|
131
|
-
## Where
|
|
159
|
+
## Where all this is written
|
|
132
160
|
|
|
133
|
-
|
|
161
|
+
Everything above is produced by `src/frontend/layouts/base.njk`, which looks up
|
|
162
|
+
the current page's entry like this:
|
|
134
163
|
|
|
135
164
|
```njk
|
|
136
165
|
{% set pageKey = title %}
|
|
137
166
|
{% set pageData = pages[pageKey] %}
|
|
138
167
|
```
|
|
139
168
|
|
|
140
|
-
|
|
169
|
+
You only need to edit `base.njk` for something the boilerplate doesn't cover — a
|
|
170
|
+
web font, an analytics snippet. For everything documented here, change the values
|
|
171
|
+
in `site.json` and `pages.json` instead.
|
|
141
172
|
|
|
142
|
-
> ⚠️
|
|
173
|
+
> ⚠️ Paths in `base.njk` go through `| url`. That filter makes them work when the
|
|
174
|
+
> site is published in a subfolder rather than at the root of a domain — GitHub
|
|
175
|
+
> Pages being the common case. Remove it and every stylesheet and script breaks
|
|
176
|
+
> there.
|
|
143
177
|
|
|
144
|
-
##
|
|
178
|
+
## Files for search engines and AI
|
|
145
179
|
|
|
146
|
-
|
|
180
|
+
Three files are generated automatically and published with your site:
|
|
147
181
|
|
|
148
|
-
| File |
|
|
182
|
+
| File | What it's for | Reachable at |
|
|
149
183
|
|---|---|---|
|
|
150
|
-
| `llms.
|
|
151
|
-
| `robots.
|
|
152
|
-
| `sitemap.
|
|
184
|
+
| `llms.txt` | Describes your site and lists its pages, for AI models | `yoursite.com/llms.txt` |
|
|
185
|
+
| `robots.txt` | Tells search engines what they may and may not crawl | `yoursite.com/robots.txt` |
|
|
186
|
+
| `sitemap.xml` | Lists your pages so search engines find them all | `yoursite.com/sitemap.xml` |
|
|
187
|
+
|
|
188
|
+
They are built from templates in `src/frontend/indexing/`. `llms.txt` and
|
|
189
|
+
`sitemap.xml` build their page lists the same way: they go through every page and
|
|
190
|
+
leave out drafts, the 404 page, and anything marked `noindex`. Creating a page
|
|
191
|
+
with the assistant is enough for it to appear in both — there is no list to keep
|
|
192
|
+
up to date by hand.
|
|
153
193
|
|
|
154
|
-
|
|
194
|
+
To change the wording or the fixed parts, edit
|
|
195
|
+
`src/frontend/indexing/llms.njk` or `src/frontend/indexing/robots.njk`.
|
|
155
196
|
|
|
156
|
-
|
|
197
|
+
> These three files must stay publicly reachable. If you block them in your server
|
|
198
|
+
> config, search engines and AI models can't read them.
|