AbsoluteJS

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:

1
Find the page routes
The build loads your exported Elysia app with the TypeScript compiler and finds every .get and .head route that renders a page.
2
Record each page’s contract
Each page gets an identity and a contract: the shape of its props type. Two routes that render the same page share it.
3
Package the client code
Each page’s client code and styles are copied into the app under content hashes, with your static assets.
4
Write the app manifest
A manifest lists the routes, pages and entry route, your production server, deep-link hosts and the device features in use.

A route counts as a page when:

The route is a .get or .head call with a literal path, such as ‘/products/:id’.
The route’s handler calls handleReactPageRequest, handleSveltePageRequest, handleVuePageRequest, handleAngularPageRequest, handleHTMLPageRequest or handleHTMXPageRequest by name.
The handler’s argument is an object literal, with index: asset(manifest, ‘…’) for the page’s client bundle.
Props are plain JSON: strings, numbers, booleans, null, arrays and objects. Dates and class instances arrive as JSON.
TS
.get('/products/:id', async ({ params }) =>
  handleReactPageRequest({
    Page: Product,
    index: asset(manifest, 'ProductIndex'),
    props: { product: await loadProduct(params.id) }
  })
)
Frameworks
React, Svelte, Vue, Angular, HTML and HTMX pages are packaged into the app. Ember and Astro support is coming soon; until then the build stops with a message naming the page.

#Inside the app

The packaged interface is written to .absolutejs/mobile/web (change it with bundleDirectory) and copied into the native projects:

index.htmlThe page every launch starts from, with a Content-Security-Policy that only allows scripts packaged in the app.
absolute-mobile-bootstrap.jsThe shell: navigation, deep links, Back, sign-in, offline data and updates.
absolute-mobile-manifest.jsonRoutes, pages, the entry route, your production server, deep-link hosts and the device features the build found.
pages/ and styles/Each page’s client code and styles, named by content hash.
assets/, html/ and htmx/Your static assets, plus HTML and HTMX pages as documents that are checked against their hashes when they load.

#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.

TXT
GET https://shop.example.com/products/42
Accept: application/vnd.absolute.page+json
x-absolute-mobile-page-id: react:ProductIndex
JSON
{
  "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 doesOfflineWhy
Launching the appWorks offlineThe interface is packaged in the app.
Opening a pageNeeds your serverThe app shows “You are offline. Reconnect to load this page.” with a Retry button.
Reading data kept in @absolutejs/syncWorks offlineStored encrypted on the device for the signed-in user.
Writing data through @absolutejs/syncWorks offlineQueued 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:

ReasonMeaning
app-releaseThe installed app is older than the releases your server keeps.
page-contractThe page the app asked for is not in any release your server keeps.
runtimeThe native shell changed in a way an older app cannot use.
protocolThe app speaks an older version of the page protocol.
Keep releases between builds
Releases are kept in 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.
TS
// 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:

YAML
# 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-

#Security

No remote codeProduction apps never load their interface from a URL. Scripts come only from the app, and the production Capacitor config may not point at a server, allow cleartext or widen navigation.
One HTTPS serverproductionOrigin must be HTTPS (plain http is allowed only on localhost in development) and may not contain credentials, a path, a query or a fragment.
Locked-down connectionsThe app may only connect to itself and your production server, over HTTPS and its secure WebSocket.
Tokens stay nativePage code never sees access or refresh tokens. The shell adds credentials to requests for your server and nowhere else.
Permissions follow importsUnused device features install nothing and request no permissions. Prompts appear only when your code asks.
Release checksabsolute mobile doctor release fails a build that still contains dev servers, debug flags, cleartext traffic or development certificates.