@clickmax/rsbuild-plugin
The build setup for websites made on Clickmax. One plugin configures Rsbuild with React 19, Tailwind CSS 4 and TanStack Start, renders every page to static HTML at build time, and packages the result so Clickmax can publish it.
You do not need to configure React, Tailwind, routing or output paths yourself. The whole configuration of a Clickmax site is:
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core'
import { clickmax } from '@clickmax/rsbuild-plugin'
export default defineConfig({ plugins: [clickmax()] })
rsbuild dev # local development with hot reload
rsbuild build # production build: dist/client, dist/server and the publishable dist/clickmax/
The matching TypeScript settings ship with the package; a site's tsconfig.json only needs its own include list and import aliases:
{
"extends": "@clickmax/rsbuild-plugin/tsconfig",
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": { "paths": { "#/*": ["./src/*"] } }
}
How a site is structured
| Path | Purpose |
|---|---|
src/routes/ | File-based routes. index.tsx is /, about/team.tsx is /about/team, _layout.tsx wraps children without adding a URL segment, posts/$slug.tsx is a dynamic route. |
src/routes/__root.tsx | The HTML document shell (<html>, <head>, <body>). |
src/styles.css | Global stylesheet, imported with ?url from the root route. Tailwind utilities are available everywhere under src/. |
public/ | Files copied verbatim to the site root: images, fonts, robots.txt, downloadable documents. Reference them with absolute paths such as /images/logo.png (prefixed with basePath when the site is not mounted at /). |
src/assets/ (or anywhere under src/) | Images and other files imported from code. They get a content hash in their name and are cached forever. |
New routes are picked up automatically; run rsbuild build (or keep rsbuild dev running) to regenerate the route tree.
What rsbuild build guarantees
The site is published as static files; there is no server at runtime. The build enforces that:
- Every route that has a component and no path parameter is rendered to
<route>/index.html. Dynamic routes ($slug) are rendered only for the paths you list inprerender.paths. - Links between pages (
<a href>) are followed and the linked pages are rendered too. Links to other websites and to files inpublic/are left alone. - Every image, stylesheet, script, font or document a page references by a same-site URL exists in the output. A missing file fails the build with the page and the URL that referenced it.
- A route that fails to render (for example a loader that throws) fails the build. Nothing is published partially.
- Data can be fetched inside a route
loaderwithfetchfrom a public API. Loaders run at build time to render the page and again in the browser on client-side navigation, so they must not use Node-only APIs such asfs. Server functions (createServerFn) and server route handlers are not supported: there is no server once the site is live. Client code that uses server functions, and pages that link to API routes, fail the build with the file or URL involved. - No server code is published.
dist/server/exists only to render pages during the build.
The published output is listed in dist/clickmax/manifest.json (routes, files, sizes and hashes). dist/clickmax/site.tar.gz is the archive Clickmax receives.
Make controls discoverable
The funnel lists what each published route renders, read from the route's final HTML into
dist/clickmax/manifest.json. Only elements marked by Clickmax runtime components count: each one
writes data-cx-component and data-cx-entity-id on its root element, and the build records the
element under its DOM ID. A custom trigger is declared with CxTrigger from
@clickmax/rsbuild-plugin/runtime/core:
import { CxTrigger, useCxTrigger } from '@clickmax/rsbuild-plugin/runtime/core'
<CxTrigger id="read-70" label="Read 70% of the page" />
// In the site's own code, when the condition is met:
const fire = useCxTrigger('read-70')
fire()
Events leave the browser only through ClickmaxProvider, rendered once in the root route around
<Outlet /> (the Clickmax site template already does this). Without it, CxTrigger is listed in
the manifest but never fires.
data-cx-component | Listed as |
|---|---|
button | Button click |
link | Link click |
visible | Element visibility |
lead-form, form | Form submission and contact capture |
trigger (CxTrigger) | Custom trigger, named by its label |
Rules the build enforces, failing with the route and the element when broken:
- Every marked element has an
idof 1-255 characters without whitespace, control characters or commas, unique across the rendered document. The same ID may be reused on different routes. Pass IDs explicitly; never generate them withuseId(), which changes between builds. - A
CxTriggerlabel has at most 255 characters and no line breaks. The funnel shows it as-is. - One element carries either a runtime marker or a Browser SDK annotation, never both.
- Unknown
data-cx-componentvalues fail; do not write the attribute by hand.
Render marked elements during prerender: client-only modals and computed elements are not in the
HTML the build reads. Use aria-label or title for a useful label; otherwise the build uses the
visible text, a name, an input value or the ID. Checkout embeds and Vturb players are recorded as
integration discoveries, which do not prove purchase or video-event support.
Browser SDK annotations (data-cx-track, form[data-cx-ingest-form], cm-* classes) keep working
in the browser but are not listed for the funnel. The build prints
capability warning legacy_selector with the route and IDs of each one it finds.
Navigation between published pages
Use normal TanStack Link, Navigate, useNavigate() and router.navigate() calls, or native
anchors. The site owns its destinations. Funnel arrows represent the funnel manually and never
rewrite button, link, form or checkout navigation.
The plugin automatically observes the router returned by your existing getRouter factory when
the published Browser SDK is active. No tracking helper, action wrapper or router registration is
required in application source. Hydrated client navigation and prerendered direct entry both work;
do not force document reloads for tracking.
Options
All options are optional. The defaults match the Clickmax site template.
clickmax({
srcDirectory: 'src', // where the application code lives
basePath: '/', // mount path of the site, e.g. '/landing' for a sub-path deployment
prerender: {
paths: ['/posts/hello'], // concrete paths to render for dynamic routes such as /posts/$slug
crawlLinks: true, // follow same-site links in rendered pages
concurrency: 4, // pages rendered in parallel
},
plugins: [], // extra Rsbuild plugins, registered after the platform ones
limits: { // local packaging limits; the platform enforces its own
maxFiles: 20_000,
maxFileBytes: 100 * 1024 * 1024,
maxTotalBytes: 1024 * 1024 * 1024,
},
})
basePath also sets the router basepath and the asset URL prefix. Public files are served under it too: with basePath: '/landing', the file public/images/logo.png is /landing/images/logo.png, and a reference to /images/logo.png fails the build because it would 404 once deployed. Links (<a href>) to paths outside basePath are allowed; they are treated as another site on the same host and are not followed.
What you can and cannot override in rsbuild.config.ts
Other defineConfig keys (for example source.alias, source.define for public constants, tools.rspack) work as usual. The keys below are owned by the platform; setting them fails the build with a message naming the key:
| Key | Reason |
|---|---|
output.distPath, environments.*.output.distPath | The platform reads dist/client and dist/server |
output.assetPrefix, server.base | Use basePath |
server.publicDir | Public files always come from ./public |
output.cleanDistPath: false | Stale files would be published |
source.define mentioning CLICKMAX_PUBLISH* or process.env | Credentials must never reach the browser bundle |
Environment
The plugin reads these variables. You normally do not set them yourself; the Clickmax platform does when it runs your build.
| Variable | Effect |
|---|---|
CLICKMAX_SANDBOX=1 | Development inside the Clickmax sandbox: the dev server listens on port 10143 on all interfaces and enables the editor preview integration. |
MXD_STUDIO_ORIGIN_SUFFIXES | Which Clickmax origins may drive the editor preview. Set by the platform. |
CLICKMAX_SKIP_VERSION_CHECK=1 | Skips the once-per-build lookup of the latest plugin version on npm (a warning is printed when a newer one exists; the build never depends on it). |
CLICKMAX_BASE_PATH | Overrides the authored mount for a production build, including router and asset URLs. Use the basePath option for development. |
CLICKMAX_PUBLISH_SOURCE_IDENTITY | Optional platform-supplied source fingerprint bound to a publication retry. |
CLICKMAX_PUBLISH_ATTEMPT_ID, CLICKMAX_PUBLISH_TOKEN, CLICKMAX_PUBLISH_API_URL | Present only in a build the platform runs to publish. After packaging, the build uploads site.tar.gz; success means the platform accepted it for processing. The three must be set together, and only for rsbuild build. |
Without the publish variables a build does no network I/O apart from the version lookup above. Credentials are read in the Node build process only and are never written into the output.
Retrying an upload without rebuilding
If a build succeeded but the upload failed, the artifact stays in dist/clickmax/. With fresh credentials in the environment:
import { publishArtifact } from '@clickmax/rsbuild-plugin'
await publishArtifact() // verifies dist/clickmax/ against the build's own record, then uploads
Retries reuse the original attempt, mount and prepared bytes. Renewing the token is allowed;
changing the binding requires a new publication attempt. Do not rerun a publication build to
retry an upload. dist/clickmax/status.json records local progress; acceptance is not proof the
site is live.
Errors
Every error thrown by the plugin has a stable code and a message that says what to change:
ClickmaxConfigError: a configuration conflict, raised before compiling.ClickmaxArtifactError: the build output cannot become a complete site (missing reference, server functions, unsafe file).ClickmaxPublishError: upload failed;stagesays where (env,prepare,upload,complete) andretryablewhether trying again later makes sense.
Supported toolchain
- Node.js 24.16 or newer
@rsbuild/core^2.2@tanstack/react-start1.168.60 or newer (1.x)- React 19, Tailwind CSS 4
License
GPL-3.0-only. See LICENSE.