Desktop and mobile app
A Récit site becomes a native application with one app: section and one command,
recit app build. The application embeds the site exactly as recit-site build produced it
(dist/) in a shared Tauri 2 shell, and serves it offline. Nothing is added to
the site: the pages do not know they run in a window.
Install
Bash
@recit/app carries the shell and @tauri-apps/cli; recit loads it on demand, as it loads
@recit/motion-render for recit render. Without it, recit app stops with "install
@recit/app". The CLI comes from @recit/cli, as usual.
The app: section
A Récit section of recit.config, next to
publish. Only identifier has no safe default when the site has no canonicalUrlOrigin.
recit.config.ts
| Key | Default | Meaning |
|---|---|---|
productName | site.title, else the name of the site folder | name of the application and of its window |
identifier | canonicalUrlOrigin reversed: https://docs.example.com → com.example.docs | reversed domain, at least two segments of letters, digits and underscores (no hyphen: Android rule). Final once published: the stores and the updater key on it |
version | version of the site's package.json, else 0.0.0 with a warning | the version the system shows |
icon | metadata.favicon of the site | square source, PNG of 1024 px or more or SVG, relative to the site; every platform size is derived from it |
window.width, window.height | 1280, 800 | size of the window at first launch |
window.minWidth, window.minHeight | none | minimum size |
window.resizable | true | — |
allowedOrigins | [] | origins the window may load besides the bundle; every other link opens in the system browser |
deepLink | none | URL scheme that opens the application (lorem-docs://…) |
android.minSdkVersion | 24 (Android 7.0) | see Android |
ios.developmentTeam | none | Apple team of the signature; see iOS |
updater.endpoints, updater.pubkey | none | automatic updates on desktop, opt-in; see Updates |
Names
The window shows productName exactly as written. The files on disk follow two rules of the
systems:
- the Linux packages (
.deb,.rpm) are named after the ASCII form ofproductName, because Debian and RPM refuse a package name outside ASCII:Récit Docsgivesrecit-docs, and the diagnosticapp-product-name-asciisays so. A name with nothing left in ASCII takes the name below; - the binary is named after the identifier without its first segment, in lowercase with hyphens
(
mainBinaryNameof Tauri):com.example.loremipsumgivesexample-loremipsum, so two applications installed on the same machine never share/usr/bin/recit-app.
Diagnostics
recit app resolves the section before anything else and prints the problems like recit build. An
error stops the build; a warning does not.
| Code | Severity | When |
|---|---|---|
app-missing | error | no app: section; the message gives the minimal section to add, with the computed defaults |
app-identifier-missing | error | neither app.identifier nor canonicalUrlOrigin |
app-identifier-invalid | error | an identifier that breaks the rule above (a hyphen, one segment, a digit first) |
app-base-path | error | the site sets basePath: the application serves the site at its root, the router and the search index expect the prefix |
app-cdn-url | error | the site sets cdnUrl: the assets would come from the network |
app-version-missing | warning | no app.version and no version in the site's package.json; 0.0.0 is used (Android refuses it) |
app-product-name-ascii | warning | productName has characters outside ASCII: the packages take its ASCII form, the binary the identifier (see Names) |
app-search-remote | warning | search.type: "inkeep": that search calls a remote service; Pagefind works offline |
config-invalid | error | the recit.config itself is invalid, one diagnostic per problem |
Commands
| Command | Options | Effect |
|---|---|---|
recit app build [dir] | --target desktop|android|ios, --skip-site-build | recit-site build in the site, then the native build; the installers are copied to .recit/app/bundle/ |
recit app dev [dir] | — | reuses a recit-site dev already listening on the site's port, otherwise starts one, then opens the window on it, with HMR |
recit app preview [dir] | --port <port> | serves dist/ in the browser exactly as the application resolves it (deep reload, 404 fallback), without Rust |
recit app android <init|dev|build> [-- …] | arguments after -- go to tauri android … | see Android |
recit app ios <init|dev|build> [-- …] | arguments after -- go to tauri ios … | see iOS |
recit app icon [source] | — | regenerates the icons of every platform from app.icon, or from the given file |
dir defaults to the current folder. Everything generated lives in <site>/.recit/app/: the shell,
its tauri.conf.json (generated at every call, never edited by hand), the icons, the Cargo
target/ and, after a build, the installers in bundle/. The folder is ignored by git.
RECIT_APP_TARGET_DIR moves the Cargo cache out of the site, to share it between the sites of a
monorepo: the second site does not compile Tauri again, and its installers are still copied to its
own .recit/app/bundle/ (Tauri writes them under the shared target directory, where the next build
of another site overwrites them).
A site of a pnpm workspace adds the command to its package.json:
package.json
Prerequisites (desktop)
Rust stable, installed at user level with rustup, and the libraries of the system's webview:
- Linux (Debian, Ubuntu):
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libssl-dev libxdo-dev pkg-config file curl wget. A machine without a display links the application all the same; to start it there (a smoke test), addxvfb,xauthanddbus, and run it underxvfb-run -a dbus-run-session -- <binary>: without a session bus, GTK waits 25 seconds before the window appears. - macOS: Xcode Command Line Tools (
xcode-select --install). - Windows: Visual Studio Build Tools with the C++ workload; WebView2 is part of Windows 10 and 11.
The first build compiles the shell and its dependencies, a few minutes; the next ones reuse the Cargo cache.
What works offline
The bundle of recit-site build has everything the site needs, so the application does too:
- the pages, one HTML file per route, and the client router after the first one;
- the Pagefind search, index and WebAssembly included;
- the fonts, the code highlighting, the math and the diagrams, all in the bundle;
- the stories the site publishes: decks and films play in the window.
A reload on a deep page finds its HTML file; an unknown address falls back on index.html, so the
site's 404 page shows. recit app preview reproduces that resolution in the browser, to check a
site before installing Rust.
Sizes
Measured on the example site examples/site-embed (a few pages, one deck, Pagefind), built on Linux
x86_64 with Tauri 2.12:
| Artefact | Size | Note |
|---|---|---|
| binary | 25 MB | the shell and the whole dist/, embedded |
.deb, .rpm | 13.4 MiB | compressed; the system provides WebKitGTK |
.AppImage | 105 MiB | carries WebKitGTK and GTK, so it runs on any distribution |
Android .apk | 21.7 MB | universal, signed; see Android |
Android .aab | 15.2 MB | the store builds a smaller .apk per device from it |
A site grows these numbers by the size of its dist/: pages, images, fonts and the search index.
Limits
basePathandcdnUrlare refused (see the diagnostics): the application serves the site at its root, from its own files.- No server in the application: no SSR, no API. A site built with
--experimental-ssris a hosting matter. - Links to other origins open in the system browser, the window stays on the site. To load another
origin inside the window, list it in
allowedOrigins. - The smartphone remote of a deck needs the development server: there is none in the application. The presenter console opens in a second window.
recit render(MP4) stays a Node and Chromium pipeline, outside the application.
Updates
On desktop, updates are opt-in: the updater section names the endpoints that serve latest.json
and the public key that signs the binaries (tauri signer generate; the private key stays a secret
of the release workflow). Without the section, the shell ships without the updater. On Android and
iOS, the stores update the application; updater is ignored there.