How Native Apps Work
The app carries your interface; your server carries the data. Here is how pages get into the app, how each one gets its data, and why a server deploy never breaks the apps people already have installed.
#Finding your pages
absolute start, compile and absolute mobile build all run the same steps when mobile is configured:
A route counts as a page when:
.get('/products/:id', async ({ params }) =>
handleReactPageRequest({
Page: Product,
index: asset(manifest, 'ProductIndex'),
props: { product: await loadProduct(params.id) }
})
)#Inside the app
The packaged interface is written to .absolutejs/mobile/web (change it with bundleDirectory) and copied into the native projects:
#Page data
When the app opens a route, it asks your production server for that route with a page media type. The same handler that renders HTML for a browser runs, and AbsoluteJS returns its props as JSON instead. Your route’s guards, auth and database queries all run exactly as they do for the web.
GET https://shop.example.com/products/42
Accept: application/vnd.absolute.page+json
x-absolute-mobile-page-id: react:ProductIndex{
"protocol": 1,
"response": {
"kind": "page",
"framework": "react",
"pageId": "react:ProductIndex",
"props": { "product": { "id": "42", "name": "Field Jacket" } },
"status": 200
}
}The packaged page then renders those props on the device. Moving between pages keeps the latest request and cancels the rest, and the screen changes only once the next page is ready, with a view transition where the platform supports one. Navigation & UI covers links, Back and sheets.
#Offline
The app starts with no network, and data you keep in @absolutejs/sync stays usable offline. Page props come from your server each time a page opens.
| What the user does | Offline | Why |
|---|---|---|
| Launching the app | Works offline | The interface is packaged in the app. |
| Opening a page | Needs your server | The app shows “You are offline. Reconnect to load this page.” with a Retry button. |
| Reading data kept in @absolutejs/sync | Works offline | Stored encrypted on the device for the signed-in user. |
| Writing data through @absolutejs/sync | Works offline | Queued on the device and applied exactly once when the network returns. |
Auth, Sync & HTTP shows how to keep data on the device.
#HTML and HTMX pages
HTML and HTMX pages are packaged as documents and checked against their hashes when they load. In HTMX pages, hx-get, hx-post and the other request attributes, and form actions, are pointed at your production server. Fragments your server returns are cleaned before they are inserted: scripts, iframes, inline event handlers, hx-on and links to other origins are removed.
#Older installed apps
People update apps when they get round to it, so your server answers more than one version. Each build keeps the server code of the three most recent releases. When an app from an earlier release asks for a page, the server answers with that release’s own code, so it receives the props it was built for, even after you have changed them.
An app older than that receives HTTP 426 and shows “This app version must be updated to continue.” The response says why:
| Reason | Meaning |
|---|---|
| app-release | The installed app is older than the releases your server keeps. |
| page-contract | The page the app asked for is not in any release your server keeps. |
| runtime | The native shell changed in a way an older app cannot use. |
| protocol | The app speaks an older version of the page protocol. |
build/.absolutejs/mobile-compatibility (under your buildDirectory). A build that starts from an empty directory, as most CI builds do, only knows the release it just made. Point mobile.compatibility.store at a blob store and every build reads the history from it and adds its own release; releases an existing build directory already has are copied in on the first build.// mobile.compatibility.ts
import { S3Client } from '@aws-sdk/client-s3';
import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
export default awsS3BlobStore({
bucket: 'shop-mobile-releases',
client: new S3Client({ region: 'us-east-1' })
});
// absolute.config.ts
mobile: {
// ...
compatibility: { store: 'mobile.compatibility.ts' }
}Without a store, restore the folder from the previous build before building:
# CI: restore the previous build’s mobile releases before building
- uses: actions/cache@v4
with:
path: build/.absolutejs/mobile-compatibility
key: mobile-releases-${{ github.run_id }}
restore-keys: mobile-releases-