Skip to main content

split-micro-app

Use for standalone micro apps: React Router SSR/SSG, Cloud overlays, Workers, CDN/R2, SEO/OG, gateway routes, previews, Docker integration and SSR bundle isolation.

Quellinformationen

Repository
lobehub/lobehub
Letzte Quellaktivität
28. September 2026 um 09:38
Erkannte Sprache von SKILL.md
Englisch
Sterne
83.263
Forks
15.990

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
split-micro-app
description
Use for standalone micro apps: React Router SSR/SSG, Cloud overlays, Workers, CDN/R2, SEO/OG, gateway routes, previews, Docker integration and SSR bundle isolation.
user-invocable
false
# Micro App: Split, SSR, SEO, Gateway Canonical living examples, both extracted 2026-08 — read the real files, this skill records the decisions and landmines, not copies of the code: - **`apps/workbench`** (`/verify`, `/acceptance`) — all code in this repo, builds and deploys from OSS CI. - **`apps/share`** (`/share/t/:id`, `/share/page/:id`) — renders Cloud-only surfaces, so it builds and deploys from **lobehub-cloud** CI. See §1b before touching it. - **`apps/auth`** (`/signin`, `/signup`, …) — the SSG variant: `ssr: false` + `prerender`, so the worker carries no React at all (7KB). 18 locales x 4 routes of prerendered documents. Also renders Cloud-only surfaces (§1b). See §3b. ## Hosting One app, two serve paths. Do not mix them. | Surface | Who serves it | Build | | ------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cloud | Gateway (torii) → Worker (SSR) | Cloud Next does **not** `build:spa:<name>`, copy `_spa-<name>`, rewrite to `/spa-<name>`, or CDN-upload that prefix | | OSS / 子部署 Docker | Next in the same image, client-rendered | `build:docker` runs `build:spa:<name>`; `<NAME>_REQUIRED=1` on `generateSpaTemplates`; Dockerfile copies `apps/<name>/package.json` before `pnpm i` and `public/_spa-<name>` into the runtime image | `generateSpaTemplates` skips a missing `dist/<name>` HTML unless `<NAME>_REQUIRED=1`. Cloud must skip. Docker must require. Do not revive Cloud `SPA_TARGET=<name>` Vite builds or a `/spa-<name>` middleware rewrite. The Docker chain per app, all five links or the route is dead: root `build:docker` script → `copySpaBuildCore.ts` target entry (`dist/<name>` → `public/_spa-<name>`) → `spaHtmlPaths.ts` resolver + `generateSpaTemplates` block → `src/app/spa-<name>/[locale]/[[...path]]/route.ts` → middleware rewrite (`src/libs/next/<name>Routes.ts` + `define-config.ts`) plus the path in `src/proxy.ts`'s matcher and, for public pages, in `isPublicRoute`. **Self-hosted loses per-page SSR.** The Next shell serves the built SPA HTML with a brand OG card and `noindex, nofollow`; per-subject title/OG only exists in the Worker build. That is the accepted trade, not a bug to chase. **Dev shells — a 200 that lies.** A `/spa-<name>` handler may only call `fetchViteDevTemplate('/index.<name>.html')` if that file exists at the repo **root** (workbench has one; it differs from `apps/<name>/index.html` only in the entry path, which must point at `/apps/<name>/src/entry.tsx`). Miss it and Vite's HTML fallback answers **200 with the main SPA shell** — the micro app never loads, nothing errors, and the main SPA no longer has those routes. An app developed against its own Vite server (share: `dev:spa:share`) carries **no dev branch at all**. `scripts/spaDevShells.test.ts` guards both directions. ## Crossing from the main SPA Only markdown internal entity links leave the main SPA (`InternalEntityLink` → `window.location.assign` when `shouldHardNavigateToWorkbench`). `Link` and `useWorkspaceAwareNavigate` stay in-router. Electron never hard-navs (portal). Do not add a `__WORKBENCH__` Vite define — the helper keys off the path (and skips Electron). Share needed none of this: the only producer builds an **absolute** URL for copy/open (`${appOrigin}/share/t/${id}` in `SharePopover`), which is already a hard navigation. Before deleting `src/routes/<x>/**`, grep for in-router links to those paths — a surviving `Link` lands on the main SPA's 404. Then drop the routes from `desktopRouter.config.tsx` / `mobileRouter.config.tsx` and update `desktopRouter.sync.test.tsx` + `routeScope.test.ts`. ## 1. Splitting & Artifacts App layout (`apps/<name>/`): ``` app/ # RR framework mode: root.tsx, routes.ts, entry.server.tsx, routes/, lib/, stubs/ workers/app.ts # worker entry: API reverse proxy + createRequestHandler src/ # app-owned features/shell (may coexist with a legacy SPA entry) vite.config.rr.mts # RR pipeline; legacy vite.config.ts can coexist (RR CLI: -c vite.config.rr.mts) wrangler.jsonc # name, account_id, nodejs_compat, vars (API base / app home) staticCssOptions.mjs # ONE source for static-css hrefTemplate (vite plugin + emit + dev middleware) ``` Reuse main-src code via `@/*` deep imports + Vite 8 native `resolve.tsconfigPaths: true` (the vite-tsconfig-paths **plugin** breaks dev SSR module-runner resolution; the app's tsconfig `include` must cover `app/`). Stack: `@react-router/dev@8` + `@cloudflare/vite-plugin` - repo Vite 8 (rolldown) — officially compatible. **SSR bundle weight is the whole battle.** Main-src imports drag the app universe (store web → chat/agent/electron). Tools and cuts, in order: | Tool / cut | How | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Trace who pulls a module | `<APP>_TRACE_MODULE=store/chat/store [<APP>_TRACE_ENV=client] bun run build:rr` — prints importer chain (`SHARE_`/`WORKBENCH_`). In an overlay repo, run it there too (§1b) | | Client-gate heavy routes | `.client.tsx` module + `clientOnlyRoute()` factory (hydration gate; SSR renders loading) | | SSR-stub store hubs | vite `resolveId` stubs per env (`app/stubs/`): trpc client, services/global, store/electron, store/file, store/user, i18n loader. Stubs must be **callable empty-state hooks**, not throwing proxies — render paths call them. Keep the implemented surface explicit (no catch-all `Proxy` / `as never`): `stubSurfaceGuard` in `vite.config.rr.mts` fails the build when the graph imports an export or member the stub does not implement. Add the member (empty / `reject`) or keep the importer off the graph. | | Client-shim lambda | Client `@/libs/trpc/client` is a cookie-only `httpLink` (`trpcClient.client.ts`). Do not ship the real `lambda.ts` — its `headers()` pulls image store → chat store + `model-bank` catalog. | | SSR-stub shiki langs | On the SSR env, resolve `@shikijs/langs` / `@shikijs/themes` / shiki's `langs-bundle` / `themes` / wasm to `app/stubs/shiki.ts`. Do **not** stub the `shiki` package entry — Pierre diffs and Highlighter import named APIs from it. | | Client shiki from CDN | On the client env, resolve `shiki` / `shiki/*` / `@shikijs/*` to pinned `https://esm.sh/...@<installed shiki version>` as externals. Do not bundle grammars or wasm into `build/client`. | | Slot-inject app-only features | Context seam owned by the shared feature (see `src/features/Acceptance/Viewer/Conversation/originConversation.tsx`); app provides, micro app leaves null → affordances hide | | Bypass barrels | Deep-path imports (`@/features/Acceptance/Acceptance`, not the barrel) — barrels evaluate sibling exports with side effects | | Compose capability atoms | Shared UI is assembled per surface; the light app never imports the fat viewer. See **`compose-atoms`**. Do not add `readOnly` on the in-app page | | Decouple dual-use components | Lift store reads to optional props (see AudioPlayer `uploadState`/`onCancelUpload`) | Share's cuts took SSR from **9.84MB → 1.78MB gzip**. Two build-level snags worth knowing: `resolve.dedupe` must include `@lobehub/ui` (the `builtin-tool-*` packages declare a loose `^5` and resolve to an older copy whose base-ui lacks components the app renders → `MISSING_EXPORT: Alert`), and a `*.client` module needs its own SSR-env stub so the hydration gate does not drag the gated tree into the worker anyway. Products: `build/client` (assets → CDN), `build/server` (worker; deploy with `wrangler deploy --config build/server/wrangler.json`). Budget: worker gzip ≤ 10MB paid. **CI affected-detection**: build emits `build-inputs.txt` (module-graph file list, gitignored); the deploy workflow diffs changed files against the manifest from the **last successful run's artifact** (carry-forward on skip), plus meta triggers (app dir, plugins/vite, lockfile, tsconfig, glob dirs). New `import.meta.glob` patterns in shared code need a new meta trigger — the one manual rule. See `apps/workbench/scripts/should-build.mjs` + `.github/workflows/deploy-workbench.yml`; the overlay-hosted variant is lobehub-cloud's `scripts/shouldBuildShare.ts` + `.github/workflows/deploy-share.yml` (§1b). The manifest is only a pre-filter: it lists every module the build loaded, so type-only files, barrels and tree-shaken code trigger builds too. The deploy decision is the **output hash** (`scripts/workerOutputHash.ts`): the build is deterministic (comment-only edits hash identically), so `deploy.ts` hashes `build/`, compares it with the `output-hash.txt` carried in the same artifact, and skips upload + `wrangler deploy` when equal — keeping Cloudflare's 100-version rollback window for real releases. Manual dispatch passes no previous hash and always deploys. Workbench has it; share does not yet. **PR-time verify is a separate workflow per repo that can change the artifact** — deploy ownership (§1b) does not decide verify ownership. Each verify builds the worker, uploads a non-deployed preview **version** (`wrangler versions upload --preview-alias`, dry-run when secrets are absent), enforces the 8MB-gzip guard, and comments the preview URL behind an HTML marker (never `--edit-last` — other workflows comment as the same bot). Workbench: one repo, one `verify-workbench.yml`. Share: **both** repos, because either side's change flows into the
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen