Single-Page Apps
Single-page apps let you version-control a front-end application (Vue, React, Svelte, anything that builds to static files) alongside the rest of your WordPress config, ship its built output to the site over the REST API, and drop it into a page with a shortcode.
Loopress ships the build output. When the app has a package.json with a build script, lps app push runs it first with the app’s own package manager (read from packageManager, else from the nearest lockfile, else npm), so a forgotten rebuild never ships a stale dist/. Without a build script, the dist/ folder is pushed as is. Loopress never installs dependencies: run the install yourself once.
How it fits together
Section titled “How it fits together”apps/ search/ loopress.app.json # a few optional settings dist/ # your build output, this is what gets pushed index.html assets/ index-9a597e0d.js index-51910369.css# 1. install the app's dependencies, oncenpm --prefix apps/search install
# 2. build and ship the bundle to WordPress (only changed files are uploaded)lps app push search
# 3. mount it in a page[loopress_app name="search"]The shortcode enqueues the app’s content-hashed entry files and prints the mount point the SPA attaches to:
<div id="loopress-app-search" data-loopress-app="search"></div>loopress.app.json
Section titled “loopress.app.json”Every field is optional.
| Field | Default | Notes |
|---|---|---|
name |
the directory name | lowercase letters, digits and hyphens |
assetsDir |
dist |
build output directory, relative to the app directory |
mountSelector |
#loopress-app-<name> |
the element id the shortcode renders and your app mounts on |
routing |
hash |
only hash is supported (see below) |
entry |
read from dist/index.html |
{ "scripts": [...], "styles": [...] }, only needed when there is no index.html |
crossOrigin |
unset | "same-origin" or "same-site": stops other sites from embedding the app’s files (see below) |
Keeping other sites from embedding your app
Section titled “Keeping other sites from embedding your app”By default, any website can load your app’s files straight from your server with <script src="https://your-site/wp-content/loopress/apps/<name>/...">. Set crossOrigin to stop that:
{ "crossOrigin": "same-origin" }On the next lps app push, the plugin writes an .htaccess in the app’s folder that sends Cross-Origin-Resource-Policy: same-origin (or same-site) with every file, and browsers then refuse to load them from another site. Remove the setting and push again to lift it.
same-originallows only your site’s exact domain.same-sitealso allows its subdomains, for examplecdn.your-site.com.- CDN on another domain. If a CDN rewrites your asset URLs to a different domain (for example
*.b-cdn.net), browsers will refuse the app’s CSS and images too. LeavecrossOriginunset in that case. - It does not hide the code. Anything a browser runs can be downloaded and copied. The setting only stops other sites from using your server to run it.
- Apache and LiteSpeed only. nginx ignores
.htaccess: add the header forwp-content/loopress/apps/<name>/in the server config instead. - Plugin version. An older Loopress plugin commits the build but ignores the setting;
lps app pushwarns you when that happens. - Your own rules win. If
apps/<name>/.htaccesson the server does not start with# Loopress:, Loopress leaves it untouched andlps app pushwarns that the setting was not applied.
Constraints
Section titled “Constraints”- Hash routing only.
vue-router/ React Router must run in hash mode (/page#/route). History-mode routing needs a server rewrite rule that would collide with WordPress permalinks and other plugins, so it is refused for now rather than half-supported. - Content-hashed filenames. Vite and webpack do this by default (
index-9a597e0d.js). It is what makes a new deploy bust every cache; the enqueue version is also set to the build id. - Static assets only.
.phpand other server-executable files are rejected on push: the bundle is served straight offwp-content/. - Per-file size limit. A single file over 8 MB is rejected (raise it with the
loopress_app_max_asset_bytesfilter). A typical search-page bundle is 1 to 3 MB total and well under this. - One generation of grace. On deploy, files from the immediately previous build are kept so a visitor mid-session does not 404 on a lazy chunk. Older builds are removed. There is no rollback or version history.
- One deploy at a time per app. Concurrent
lps app push(or a push racing alps app remove) for the same app are not serialized: the asset cleanup and the state write are separate steps. Deploys of different apps are independent. Run one push per app at a time, which is the normal case for a CI pipeline. - Not part of
lps push. Apps have their own build step, so the aggregatelps push/lps pull/lps promotecommands leave them alone. Deploy withlps app push.
Talking to WordPress from the app
Section titled “Talking to WordPress from the app”The shortcode exposes a small config object on the page, named loopressApp_<name> (hyphens become underscores):
// window.loopressApp_search{ name: 'search', mount: '#loopress-app-search', base: 'https://example.com/wp-content/loopress/apps/search/', buildId: '9f2a1c7b4e10', restUrl: 'https://example.com/wp-json/', restNonce: 'a1b2c3d4e5',}base is the absolute URL the bundle is served from: use it to resolve a public/ asset referenced from rendered markup (base + 'icon.svg') that the bundler does not rewrite. Pair restUrl / restNonce with a custom API route for the search endpoint itself.
- CLI: the
lps app pull/push/list/removecommands