@octanest/storefront-runtime
v0.1.2
Published
Render a whole Octanest store in any React framework: page loader, <OctanestStorefront>, head tags, access rules and web-standard /api route handlers
Readme
@octanest/storefront-runtime
Render a whole store built in the Octanest console in any React framework: the page's sections and products, the store's theme, header and footer, cart, checkout, sign-in, languages, display currency, analytics, app embeds and the shop assistant.
Next.js apps use @octanest/commerce-next, which wraps this package with Next's
request cache, Metadata and redirects. Everything here is plain data in, plain data out, with web
Request/Response for the API routes.
The four pieces
| Piece | Where it runs | What it does |
|---|---|---|
| loadStorefrontPage(input) | server | Fetches the page: sections, products, theme, menu, footer, language, currency, settings |
| storefrontAccess(page, request bits) | server | The custom app the store is live on, the merchant's redirects, or the "coming soon" / password page |
| storefrontHead(page, { origin }) | server | Title, description, canonical, hreflang, social cards, robots; storefrontMetaTags() turns it into tags |
| <OctanestStorefront chrome data api /> | server + browser | The page itself |
/api/* handlers for the cart, checkout handoff, sign-in, orders, wishlist and more are in
@octanest/storefront-runtime/routes, as (Request) => Promise<Response>.
React Router (Remix)
// app/routes/$.tsx: every page the merchant built
import {
loadStorefrontPage, storefrontAccess, storefrontHead, storefrontMetaTags,
storefrontApiFromEnv, publicStorefrontApi, preferencesFromRequest, getCookie,
OctanestStorefront, StoreHoldingPage, StorefrontUnavailable,
} from "@octanest/storefront-runtime";
import { redirect, useLoaderData } from "react-router";
export async function loader({ request, params }) {
const url = new URL(request.url);
const api = storefrontApiFromEnv();
const page = await loadStorefrontPage({ api, path: `/${params["*"] ?? ""}`, ...preferencesFromRequest(request) });
const access = await storefrontAccess(page, { api, host: url.host, search: url.search, cookie: (n) => getCookie(request, n) });
if (access.type === "redirect") throw redirect(access.to, access.permanent ? 308 : 307);
return { page, access, origin: url.origin, head: storefrontHead(page, { origin: url.origin }), api: publicStorefrontApi(api) };
}
export const meta = ({ data }) => storefrontMetaTags(data.head);
export default function Store() {
const { page, access, api, origin } = useLoaderData<typeof loader>();
if (!page.ok) return <StorefrontUnavailable reason={page.reason} />;
if (access.type === "holding") return <StoreHoldingPage {...access.page} />;
return <OctanestStorefront chrome={page.chrome} data={page.data} api={api} origin={origin} />;
}// app/routes/api.checkout-handoff.ts
import { handleCheckoutHandoff } from "@octanest/storefront-runtime/routes";
export const action = ({ request }) => handleCheckoutHandoff(request);Configuration
Server env, read by storefrontApiFromEnv() (the NEXT_PUBLIC_ forms work too):
| Variable | |
|---|---|
| OCTANEST_API_BASE_URL | the API host, e.g. https://dev.apis.octanest.com (the default). Every service address follows from it |
| OCTANEST_CATALOG_BASE_URL, OCTANEST_AUTH_BASE_URL, OCTANEST_ORDERS_BASE_URL, OCTANEST_STOREFRONT_BASE_URL, OCTANEST_AROUNDS_BASE_URL | optional: one service somewhere else |
| OCTANEST_BUSINESS | optional, default arounds_marketplace |
| STOREFRONT_ID | the store this app serves (custom apps) |
| AROUNDS_PUBLIC_URL, STOREFRONT_PLATFORM_ZONE | checkout host; platform domain for /s/{slug} |
The browser never reads env: pass publicStorefrontApi(api) to <OctanestStorefront> and the cart and
sign-in use it.
