Architecture
How Wake is put together, what happens during a request, and how it stays fast without a database.
This page explains how Wake works inside. You don't need it to run a site, but it helps when something surprises you, and it is the starting point for contributors.
The folders
wake/ Laravel application (namespace Wake\), replaced on update
content/ texts, pages, posts and media (editors)
themes/ looks (designers)
plugins/ extensions (developers)
settings/ config.php, general.yml, menus, redirects, roles, consent
data/ accounts, statistics, versions, trash, backups: back it up
temp/ caches, locks, logs: may be deleted at any time
public/ document root: index.php, media/, assets/
.env application key
wake/bootstrap/app.php tells Laravel that its public folder, its storage
folder (temp/) and .env sit next to wake/, not inside it.
Wake\Support\Paths knows the other folders; each can be moved with a
WAKE_*_PATH variable (configuration). That is why an update can replace
wake/ without touching anything that belongs to the site
(decisions 0008 and
0011).
A request
browser ─► public/index.php
│
├─ cached page in temp/cache/pages? ──► send it, done (Laravel never starts)
│
▼
Laravel ─► SiteController
│ ├─ language from the first path segment
│ ├─ Index: address → entry
│ ├─ theme template → HTML
│ └─ CachePages middleware stores the HTML
▼
response sent ─► maintenance (at most once a minute, max. 5 s)
The page cache
public/index.php looks for temp/cache/pages/{path}/index.html before it
loads anything else. If the file exists and is younger than page_cache.ttl
seconds, it is sent straight away. Such a request costs about as much as
serving a static file.
Pages are cached per scheme and host (temp/cache/pages/https_example.org/…),
so the http:// or www. version of a site never hands out links of another.
At most ten hosts are kept, so forged Host headers cannot fill the disk.
Pages are only cached when they are the same for everyone: GET requests
without a query string, without the panel's session cookie, with status 200.
The website routes don't start a session or set cookies at all.
The whole cache is thrown away whenever the index notices a change. Changes
made via FTP are noticed on the first uncached request, which is why cached
pages expire after page_cache.ttl seconds.
Languages
The first path segment selects the language when it is a language code of the
site (/de/ueber-uns). Without one, the default language applies, unless
prefix_default_language is on. Every entry has its own address per language,
built from its translated slug.
The index
A database answers "the five newest posts tagged travel" without reading
every post. Wake gets the same from its index: one PHP file,
temp/cache/index.php, that returns an array with every entry, every
language and every front matter field, but no body text.
'posts/hello-world' => [
'type' => 'post',
'name' => 'hello-world',
'order' => null,
'menu' => false,
'style' => null, // mtime of style.css, if there is one
'languages' => [
'en' => ['file' => 'posts/hello-world/content.md', 'mtime' => 1792220000, 'fields' => [...]],
'de' => [...],
],
],
Next to the entries, the index keeps the media library: every file below
content/media/ with its modification time and details, and which entries
use it (found in cover, thumbnail and the links of the text).
Because it is a PHP file, OPcache keeps it compiled in memory. Loading it takes
microseconds, and queries are plain array operations. The body of an entry is
read from its file only when a template calls content().
The index also holds an address table per language ('about/team' => 'pages/about/team'), so finding the entry for a URL is a single array
lookup.
Noticing changes
On every uncached request (with index.check set to 'always'), Wake lists
all files below content/ with their modification times and compares the list
with the one stored in the index. Only new or changed files are parsed again.
For a site with a few thousand files this takes a few milliseconds, and thanks
to the page cache it rarely happens.
When the index changes, Wake fires the ContentChanged event, which clears the
page cache.
Writing files safely
Wake never writes a file in place. Wake\Support\Files::write() writes to a
temporary file in the same folder and then renames it over the original, which
is atomic on every file system: readers see the old or the new version, never a
mix. Concurrent changes to the same entry are serialised with flock() through
Files::lock().
Maintenance without cron
Many cheap hosts offer no cron. Wake therefore uses Laravel's terminating
hook, which runs after the response has been sent to the visitor:
- At most once per
maintenance.intervalseconds (a timestamp file intemp/locks/), and never twice at the same time (a non-blocking lock). - Each task gets a deadline (
maintenance.budget) and stops early when it is reached. The rest happens on the next run.
Tasks today: publishing scheduled entries. Statistics and clean-up tasks follow
in phase 2. A scheduled entry is visible as soon as its date has passed, even
before maintenance has run; maintenance only writes status: published into
its file and clears the cache.
With maintenance.mode set to 'cron', the same tasks run from Laravel's
scheduler instead.
Media
Content folders are not public, so media files get addresses below /media:
/media/{entry hash}/{version}/{width or "o"}/{file name}
/media/f5e780b058/tn3hns/960/cover.jpg
/media/f5e780b058/tn3hns/960/cover.jpg.webp
The first request reaches Laravel, which copies or resizes the file into
public/media/ under exactly that path. From then on the web server serves it
directly. The version is the file's modification time, so a replaced image gets
a new address, and only widths from images.widths are allowed.
Markdown
Markdown is rendered with league/commonmark
plus GitHub's extensions. Wake adds the card syntax (::: name … :::) as a
block extension and rewrites relative file names in links and images to media
addresses.
Where to find things in the code
Folder in wake/app/ |
Contents |
|---|---|
Content/ |
index, entries, queries, front matter, content types |
Content/Models/ |
Post, Page, Tag |
Routing/ |
address → entry |
Http/ |
controllers and the page cache middleware |
Cache/ |
page cache |
Media/ |
media files and image resizing |
Markdown/ |
Markdown rendering and cards |
Maintenance/ |
maintenance runner and tasks |
Theme/ |
theme templates and assets |
Plugins/ |
loading plugins from plugins (plugins) |
Backup/ |
backup ZIPs and restoring them |
Localization/ |
languages |
Support/ |
atomic file writes, paths |
source: docs/concepts/architecture.md