Morphe Deploy
Overview
Deploy a
Next.js,
Bigfish (
), or
Vite project to
Morphe (runtime
, linux-x64-gnu). The runtime always starts
the function with
, so every framework packages down to a zip
whose root is a
:
- Next.js → the standalone server (
.next/standalone/server.js
) plus its
traced . Packaging prunes wrong-platform native bindings and
repairs pnpm partial packages.
- Bigfish → a static / SPA build (). Packaging generates a
zero-dependency static (Node built-ins only — nothing to
install) that serves the build dir with SPA fallback to .
- Vite → two modes. A pure SPA ( → , no server)
reuses the same zero-dependency static wrapper as Bigfish. A custom-server
app (a /, e.g. Express, that serves and
backend API routes) is esbuild-bundled into a self-contained
at the zip root, with the build dir alongside it.
The fragile, deterministic API work (auth, presign, upload, CRC64,
,
deploy) lives in
. The build/config judgment steps are done by you.
Run all
commands from the project root. Replace
below with this skill's directory (the folder containing this file).
Workflow
Execute these steps in order. Stop and report if any step fails.
1. Ensure logged in
bash
python3 SKILL_DIR/scripts/morphe.py check-auth
- Exit 0 → already logged in (valid in ). Continue.
- Exit 1 → not logged in. Ask the user for their username and password, then:
bash
python3 SKILL_DIR/scripts/morphe.py login --username "USER" --password "PASS"
Never echo the password back. On success the token is saved to
.
If login fails (e.g. HTTP 401), report the error and stop.
2. Detect the framework
bash
python3 SKILL_DIR/scripts/morphe.py detect-framework --project-root .
Prints
,
,
, or
(exit 1). Detection is ordered
(first match wins):
- bigfish — in deps, or a
exists (checked first; Bigfish projects also have next-like build scripts and
vite-style tooling).
- nextjs — in deps, or a exists.
- vite — in deps, or a
vite.config.{js,mjs,ts,mts,cjs}
exists
(checked last; it's the broadest).
If it prints
, tell the user "暂不支持该项目类型(仅支持 Next.js、Bigfish 与 Vite)"
and stop. Otherwise remember the framework — it selects the build and packaging
path below.
3. Resolve the function name
Ask the user what function name to deploy under. Tell them:
如果不知道填什么,
可以留空,会自动生成一个 格式的随机函数名。
- If already has a , mention it as the current
default and let the user keep it (just press enter) or override.
- Persist the choice (empty input → keep existing or generate):
bash
# user provided a name:
python3 SKILL_DIR/scripts/morphe.py set-function-name --name "NAME" --project-root .
# user left it blank:
python3 SKILL_DIR/scripts/morphe.py set-function-name --project-root .
The command prints the final function name and writes it to
.
This name is reused on every redeploy, so the same function is updated.
4. Validate & fix the build config
Steps 4–6 differ by framework. Follow the branch for the framework detected
in step 2.
4a. Next.js
The goal: the runtime target (
) binary of every native dep must be
installed on disk before the build, so Next's tracer can pick it up. The
step (step 6) handles top-level placement, pruning, and
zipping — but it can only ship a binary that the install actually downloaded.
Edit the config in place to ensure:
-
standalone output —
(else there is no
to zip).
-
Install the linux binaries on the build host. Native addons ship as
per-platform optional deps; a macOS install only fetches the darwin one.
- pnpm () — add so the
linux-x64-gnu binaries are fetched too. This is the single most important
fix; with it, the tracer auto-includes the binding and you usually need NO
outputFileTracingIncludes
at all:
yaml
supportedArchitectures:
os: [current, linux]
cpu: [current, x64]
libc: [current, glibc]
Then re-run .
- npm/yarn — install the specific binding(s) for the target, e.g.
npm i --no-save @resvg/resvg-js-linux-x64-gnu --force --os=linux --cpu=x64 --libc=glibc
.
-
(Optional) trim the bundle further. already prunes
every non-linux-x64-gnu native binary, so you do NOT need
outputFileTracingExcludes
for those. Only add excludes for
project data
the server doesn't need at runtime (large fixtures, raw datasets, docs). Keep
anything read at runtime via
(fonts, JSON the route reads).
See
references/nextjs-config.md
for details, per-package binding names, and
how to confirm the linux binary is on disk.
4b. Bigfish
No native-binding work — Bigfish builds a static / SPA bundle and
generates a
zero-dependency (Node built-ins only). There
is nothing to install for the linux target and no config to patch in the common
case.
- Confirm the app builds to a static bundle. A default Bigfish build
emits ( + hashed JS/CSS), which is what the generated
static server serves. (SSR) is not covered by this
static wrapper — if the app truly needs server-side rendering at runtime, stop
and tell the user the static path won't serve their SSR routes.
- If the build output dir is not (Bigfish lets you override it), note the
dir name; you'll pass it via in step 6.
4c. Vite
Vite has two modes;
(step 6) auto-detects which by probing
for a server entry (
, also under
).
-
Pure SPA (no server entry) — nothing to install or patch, exactly like
Bigfish.
emits
(
+ hashed assets) and the
generated
zero-dependency static
serves it with SPA fallback.
If the output dir isn't
, note it for
in step 6.
-
Custom-server (a
/
serving
and API
routes) —
will
esbuild-bundle that entry into a self-contained
. Make sure the server:
- listens on and binds (the runtime
sets and routes to all interfaces);
- reads its static dir from (e.g.
path.join(process.cwd(), "dist")
), since the zip ships the build dir
alongside at the runtime working dir;
- keeps any import dev-only. is always externalized from
the bundle (it's huge and pulls in native addons — esbuild/
lightningcss/fsevents — that can't be bundled and break the build). A
production server must not load it. Put any dev-middleware vite usage behind
a lazy, non-production branch, e.g.:
ts
if (process.env.NODE_ENV !== "production") {
const { createServer } = await import("vite"); // lazy: never bundled
// ... dev middleware
}
Runtime
secrets (API keys, etc.) belong in the
function environment,
not in the zip — the build never bundles your
.
- If your server needs an extra package kept out of the bundle (e.g. a native
addon esbuild can't inline), pass it via in step 6
— but then that package must be reachable at runtime (this is an edge case;
most pure-JS deps bundle fine).
5. Build
bash
npm run build # or: pnpm build / yarn build (Bigfish: also `bigfish build`)
Do NOT hand-copy assets or hand-zip — step 6 does all assembly. (Vite: run only
the
frontend build here to produce
. For a custom-server app you do
not need to bundle the server yourself —
runs esbuild for you in
step 6. If your
script already esbuilds the server, that's harmless;
re-bundles into the staged zip regardless.)
6. Assemble the minimal deploy zip
bash
python3 SKILL_DIR/scripts/morphe.py package --project-root .
auto-detects the framework (override with
--framework nextjs|bigfish|vite
). It is idempotent (safe to re-run, but re-run the build
first if you changed code).
Next.js — assembles
into a runnable zip:
- copies and into the bundle (not traced by the build);
- repairs pnpm partial packages — top-level dirs that
hold only while the real files live in (this shadowing
is what crashes with e.g. Cannot find module
@swc/helpers/cjs/_interop_require_default.cjs
);
- prunes every native binding that isn't (darwin/musl/arm64
— often 100M+) and symlinks the kept linux bindings to top-level
node_modules, where the runtime resolves them with a bare
require("<pkg>-linux-x64-gnu")
. Missing this is why SVG-style code paths work
but anything hitting the native addon 500s with Cannot find module
;
- zips with symlinks preserved () to OUTSIDE the
standalone dir (so it never nests a previous ). The runtime preserves
symlinks, and pnpm's layout is mostly symlinks into , so this roughly
halves the zip.
Bigfish — assembles a static-server zip:
- locates the build output dir (auto-probes , , for one
containing ; override with );
- generates a zero-dependency (Node // only) that
serves the build dir and falls back to for client-side routes;
- stages + the build dir and zips so is at the zip
root with the build dir alongside it. No , so the zip is small.
Vite — two modes, auto-selected by probing for a server entry:
- SPA (no server entry found) — identical to the Bigfish path above: a
generated zero-dependency static + the build dir.
- Custom-server (a entry, auto-probed; force/override
with ) — esbuild-bundles the entry
(
--bundle --platform=node --format=cjs
, always external, plus any
you pass) into a self-contained , then stages it
with the build dir (auto-probes //, override )
and zips so is at the zip root. Deps are inlined, so no
ships.
It prints the final path, size, and framework, e.g.
packaged: …/code.zip (25M)
or
packaged: …/code.zip (0.2M, framework=bigfish)
or
packaged: …/code.zip (0.7M, framework=vite, mode=server, entry=server.ts)
.
7–11. Upload, checksum, and deploy
A single command does presign → curl PUT upload → CRC64 checksum →
update
(writes
; uses the
resolved in
step 3, generating one only if somehow still absent) → call
:
bash
python3 SKILL_DIR/scripts/morphe.py deploy --zip code.zip --project-root .
On success it prints the deploy result JSON (including
,
,
and
when available), then
deletes the local (it's
already in OSS and is a large throwaway). Pass
to retain it for
inspection. On failure the zip is kept so a redeploy can retry. Report the
outcome to the user — give them the
if present. If it prints
or an HTTP error, go back to step 1 (the token may have expired)
or report the failure.
Notes
- The Morphe API advertises cookie auth but login returns an ;
the script sends it as both a cookie and a Bearer header.
- in is generated ONCE and reused on every
redeploy so the same function is updated rather than duplicated. Do not
hand-edit or regenerate it.
- All frameworks deploy with the default ,
— no per-framework deploy flags. The Bigfish / Vite-SPA static
server honors (defaults to 3000) so it matches; a Vite
custom-server must do the same (step 4c).
- (Next.js) Keeping small is mostly automatic via (binding pruning + symlink-preserving zip). The remaining large item
is usually project data the build traced in (raw datasets, fixtures,
generated outputs under a dir a server component s). Trim those with
outputFileTracingExcludes
in the Next config — but never exclude files read
at runtime via (fonts, JSON the route parses). (Bigfish zips
are just the static build — already small, no .)
- (Next.js) If a native addon still 500s at runtime with Cannot find module
: the binary wasn't installed on the build host. Fix the
install (step 4a: for pnpm, or
npm i --os=linux --cpu=x64 --libc=glibc …
), reinstall, rebuild, repackage. warns
when an expected linux binding is absent.
- (Bigfish) A blank page or 404 on a client route means the SPA fallback
isn't reaching , or the build dir wasn't . Confirm
exists in the build dir and pass if it's not one of
//. The generated static server is for static builds; an
SSR () app needs a real server entry, not this wrapper.
- (Vite) esbuild step fails (e.g. No loader is configured for
".node" files or a glob/ error from //
): the server entry imports (or another native-addon dep) at
the top level. Make that import dev-only / lazy (step 4c) so it's
externalized, or pass the dep via .
- (Vite) SPA blank page / 404 on a client route — same as Bigfish: confirm
is in the build dir and pass if it isn't
//. A custom-server route 500ing usually means a dep was
externalized but actually needed at runtime, or the server reads its static
dir from a path other than — fix per step 4c.
- (Vite) A 500 from a backend route at runtime but not locally is often a missing
runtime secret — set API keys etc. in the function environment (they're
never bundled into the zip from your ).
- Full API reference: .