Next.js owns the path
/quotes is a Next.js route. JOPS owns the hash: /quotes#details?id=q2.
A JOPS SPA can run inside a page of a Next.js (App Router) app. Next.js keeps what it does best: server rendering, data, Route Handlers, its own routing and the React ecosystem. The JOPS app adds class-based views with an Android-style lifecycle on the pages that need them. Both render on the same page, and the two share one codebase and one deployment.
Next.js owns the path
/quotes is a Next.js route. JOPS owns the hash: /quotes#details?id=q2.
Files in public/
The JOPS app’s files are served as they are. Turbopack and webpack never bundle them.
Next.js is the backend
The JOPS app calls your Route Handlers with fetch().
In an empty folder (or a fresh npm project), run init:
npx jops-core initPick 3. Next.js JOPS Embedded SPA. init writes the project into the current folder, merges its dependencies and scripts into an existing package.json (existing entries are kept), vendors the library and runs npm install.
Start the dev server:
npm run devThe template contains a React-only page and a page that hosts the JOPS SPA:
<JopsApp app="/jops-spa/App.js" />GET /api/quotes[?status=…], the SPA’s backend#login screen and a #quotes list
Install JOPS and vendor the library into public/lib/ (served as /lib/jops-core.min.js):
npm i jops-corenpx jops-core updateupdate detects a Next.js project (next.config.*) and copies the library to public/lib/jops-core.min.js. Run it again after every npm i jops-core@latest. Adding "postinstall": "jops-core update" to package.json does it automatically.
Put the JOPS app in public/, for example public/my-app/App.js, public/my-app/layout/main.html and its views. App.js is the same entry module as in a standalone app:
import { View } from "jops-core";
export default class App { constructor() { const root = new View(); root.loadLayout("/my-app/layout/main.html").then(() => root.render()); }}Render JopsApp on the page that hosts the app:
import JopsApp from "jops-core/nextjs";
export default function MyAppPage() { return <JopsApp app="/my-app/App.js" id="my-app-root" />;}JopsApp is a Client Component ("use client" is built in), so a Server Component page can use it. It renders an empty <div> on the server. In the browser it loads /lib/jops-core.min.js once per page, adds the "jops-core" import map to <head> and mounts the app into its <div>. You don’t add an import map or a script tag to the root layout.
JopsApp props| Prop | Description |
|---|---|
app |
Required. URL of the app’s entry module (/my-app/App.js). Its default export is instantiated |
id |
id of the root <div> (CSS, tests) |
className |
className of the root <div> |
lib |
URL of the jops-core library, default /lib/jops-core.min.js. The import map uses the same URL, so the page always has a single jops-core instance |
The root <div> has position: relative, which the Router’s slide and fade animations need.
A JOPS app inside a Next.js page behaves like a standalone app that the page launches:
<div> is emptied, Store and EventBus are cleared, and the history methods JOPS wrapped are restored. Nothing of the app keeps running.<Link href="/my-app"> shows the default (first) route and <Link href="/my-app#details?id=7"> shows that route. Stacked navigate() views are closed in both cases.history.state, so Next.js Back and Forward keep working.src="/my-app/Quotes.js", layout="/my-app/layout/quotes.html", loadLayout("/my-app/…"). ES module import statements inside your .js files resolve relative to the file as usual.public/lib/), not the CDN. With the CDN, JOPS turns /… paths into page-relative ones.<div class="my-app"> <Router jops> <Quotes jops path="/quotes" src="/my-app/Quotes.js" layout="/my-app/layout/quotes.html"></Quotes> </Router></div>setStyle() has no effect inside Next.js (a console message says so once).public/, so Tailwind classes in JOPS layouts and views are generated by the Next.js build."bg-sky-50" works, `bg-${color}-50` is never generated. Use a lookup table with full class names when the class depends on data.globals.css or in a CSS file imported by the page. It applies to the whole site, so avoid global rules and write .my-app table td { … }, not table td { … }.fetch("/api/…") to Route Handlers on the same origin. The session cookie is sent automatically. A JOPS app can’t call Server Actions.Router, Store and EventBus are page-wide singletons.JopsApp inside a Next.js page. Don’t add the jops-core <script> or an import map to a page or layout yourself: React doesn’t run scripts it renders on soft navigation, and nothing would end the app when the page is left.The result is a standard Next.js app. npm run build produces .next/, and the app runs on a Node.js server when it uses server features such as Route Handlers. Deploy it like any Next.js app (see the Next.js deployment docs). Points specific to JOPS:
public/ with the app. The whole JOPS app and the library live there. With output: "standalone", copy public/ (and .next/static/) into .next/standalone/ yourself.public/ are not content-hashed, so a CDN or the browser may keep serving an old jops-core.min.js or view after a deploy. Use short cache headers for those paths, or versioned paths.npx jops-core update after updating the package and before npm run build.