feat(templates): add Next.js templates (JavaScript + TypeScript) - #123
Conversation
Adds `nextjs` and `nextjs-ts` templates that run a Next.js App Router app on Harper via the @harperfast/nextjs plugin. Because the app runs inside Harper, server actions and server components read and write tables directly through the `tables` global — no separate API server and no network round-trip. Ports the "Doggy Management System" demo (schema, server actions, list/detail pages, client delete button) from the nextjs-example prior art, modernized to the current harper CLI, @harperfast/nextjs 2.x, Next.js 16, and React 19. Wires both templates into the catalog: templates.js, templates.d.ts, frameworks.js (new "Next.js" family), templates.test.js, package.json files[], and the root README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces new Next.js and Next.js + TypeScript templates for the Harper platform, including the necessary configuration, schema definitions, and sample application code. The review identified several critical issues, including the use of unreleased versions for Next.js and Node.js in package files, a bug in the form validation logic where an age of 0 is incorrectly rejected, and a missing loading state for the delete action. These issues should be addressed to ensure the templates are stable and provide a good user experience.
Adds an optional `studio` flag to the template catalog (default true) and a derived `studioTemplateNames` list. The Studio build/publish scripts now iterate `studioTemplateNames` instead of `templateNames`, and the two nextjs entries set `studio: false`. The @harperfast/nextjs plugin builds Next.js on load, and whether that works in the Studio's deploy-only model is unverified — plus first-time publish perms / npm provenance for the new packages aren't set up yet. Both are easy follow-ups once this lands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- createDog: validate the raw `age` form value instead of the coerced number so a legitimate age of 0 (a puppy) is no longer rejected as a missing field. - DeleteButton: wrap the deleteDog server action in useTransition, disabling the button and showing a pending state while it runs — the recommended React 19 / Next.js pattern for server actions invoked from client events. Verified: repo dprint/oxlint/vitest, plus scaffold tsc --noEmit and eslint for both variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the review, @gemini-code-assist. Triage below. Applied (fixed in d2bb8ff)
Not applied — the "unreleased version" flags are false positivesThe four
Downgrading to Next 15 / Node 22 would regress the templates and diverge from the rest of the catalog, so I'm keeping them as-is. 🙂 |
|
Thanks for the update, @dawsontoth. The changes look great:
Regarding the versioning: Thanks for the clarification. I understand that these versions are intentional and consistent with the existing catalog, and that downgrading would cause unnecessary divergence. I appreciate the context on the environment and the verification steps you've taken. Everything looks solid. Nice work on these templates! |
Local `harper run`/`deploy` was leaving the app unbuilt (every route 404'd) because the template's server modules had a top-level `import 'harper'`. That import breaks the plugin's on-startup production build two ways: Turbopack (Next 16's default) can't resolve the bare `harper` specifier, and even under webpack the import opens Harper's database during Next's build-time page-data collection, colliding with the running instance's LMDB lock. On failure the plugin logs and skips serving — so the app "deploys" but serves nothing (the symptom seen deploying to Studio). Harper already injects `tables` as a global into server-side code, so the import is unnecessary: drop it and use `tables` directly. With that gone the default Turbopack build succeeds and the app builds + serves under `harper run`. Verified end to end on Harper 5.1.22: both variants build + serve via `harper run`, TS type-checks, and a full create/list/detail/delete round-trip (server actions + tables read/write, incl. age 0) works through the UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kriszyp
left a comment
There was a problem hiding this comment.
Are these just copied from nextjs-example?
…arter Replaces the ported "Doggy Management System" CRUD app with a bare starter whose only Harper touch is a counter persisted in a `Count` table: a server component reads it and a server action increments it. This matches the altitude of the other create-harper templates (blank starter + schema scaffolding) and shows the one thing unique to Next-on-Harper — reading/writing `tables` from server code — without shipping a full app. Addresses the PR review (thanks @kriszyp): dropping the CRUD surface removes the unbounded table scan, the unauthenticated destructive `deleteDog` action, the no-confirm delete button, and the bypassable form validation. The remaining mutation is a single-key, non-destructive counter increment (no scan, no user input); the README notes that any client-reachable action needs its own authz. Removed: app/dogs/**, app/ui/DeleteButton, app/not-found. Both JS and TS variants updated (+ harper.d.ts, READMEs). Verified on Harper 5.1.22: both variants build + serve under `harper run`, TS type-checks, and the counter increments and survives a restart (0 -> 1 -> 2, still 2 after restart) — confirming it's persisted in the table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yep — originally ported straight from That removes the surface behind each of your notes:
Also worth flagging: a separate fix ( |
Adds `nextjs` and `nextjs-ts` to the integration test matrix so CI installs, lints, formats, builds, and boots them like every other template. Two accommodations for the Next.js templates: - The `run test` step is now conditional on a `test` script existing — the Next.js templates ship no unit tests (the runtime smoke is their coverage), while every existing template still runs its tests unchanged. - A dedicated template.tests/nextSmoke.js replaces runtimeSmoke.js for the Next.js variants: they route through the @harperfast/nextjs plugin (no REST resource surface), so the smoke boots the app under Harper and asserts the on-startup build serves `/` and renders the Harper-backed counter. It builds single-threaded for determinism and uses the same isolated-/tmp-root, process-group-cleanup conventions as runtimeSmoke.js. Verified locally on Harper 5.1.22: scaffold → install → lint → format → build (cold `next build`) → nextSmoke all pass for both variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…yarn The integration matrix surfaced two package-manager-specific failures the npm path masked: - pnpm `install` exited non-zero with ERR_PNPM_IGNORED_BUILDS for Next.js's native deps (sharp, unrs-resolver). Added `pnpm.onlyBuiltDependencies` to approve exactly those builds (npm/yarn ignore the field). - `eslint .` crashed under yarn/pnpm with "Cannot find module 'typescript'": eslint-config-next pulls typescript-eslint, which requires typescript. npm happened to hoist it transitively; yarn/pnpm don't. Declared `typescript` as a devDependency in the JS template (the TS template already had it) so the Next.js lint config resolves under every package manager. Verified locally with pnpm 10 and yarn 1: scaffold → install → lint → build all pass for both variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Builds The previous package.json `pnpm.onlyBuiltDependencies` field was a no-op on the CI's pnpm (11.13.1): pnpm v11 removed that setting and replaced it with `allowBuilds` in pnpm-workspace.yaml, so `pnpm install` still hard-failed with ERR_PNPM_IGNORED_BUILDS for sharp/unrs-resolver. (My local pnpm 10.32 only warned, which masked it.) Ship a `pnpm-workspace.yaml` with `allowBuilds` in both templates instead, and drop the dead package.json field. Verified with the pinned CI version via `npx pnpm@11.13.1`: a freshly scaffolded nextjs-ts installs, lints, and builds clean (EXIT 0); pnpm 10.32 also tolerates the key; npm and yarn ignore the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deploying the Next.js templates with the default server-side build 404s on a multi-threaded Harper cluster: worker threads run `next build` into the same `.next` concurrently and race (ENOENT on `.next/BUILD_ID`), so the plugin never serves. See HarperFast/nextjs#52. Until the plugin coordinates the build across threads, ship a prebuilt app. - config.yaml: `prebuilt: true` on `@harperfast/nextjs` (serve a prebuilt `.next` instead of building on startup). - scripts: `dev`/`start`/`deploy` now run `next build` first, so `.next` exists before Harper serves it (the plugin honors `prebuilt` even in dev, so a bare `harper dev` would otherwise bail on a missing `.next`). `deploy_component` uploads `.next` because it doesn't consult `.gitignore` — only excludes node_modules — so `.next` can stay gitignored. - nextSmoke.js: drop the forced THREADS_COUNT=1 so CI exercises multi-threaded serving and would catch a regression back to an on-startup build. - README/config comments updated to describe the prebuilt deploy. Verified on the stage cluster: prebuilt deploy serves and the counter reads + writes live (browser). Locally: scaffold → install → build → multi-thread nextSmoke passes for both variants; `next build && harper dev` serves with HMR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The counter's read-then-write (`get` then `put value+1`) loses updates when requests overlap — two workers read 7 and both write 8. Since this starter deploys replicated and CI now runs multiple Harper workers, switch to Harper's atomic delta inside a transaction (thanks @kriszyp): await transaction(async () => { const record = await tables.Count.update('count'); record.addTo('value', 1); }); `addTo` is safe across worker threads and replicated nodes, and the transaction creates the row on first use. Both `transaction` and `tables` are Harper runtime globals — no `import 'harper'` (which would break the production build). Mirrored across JS + TS: server actions, `harper.d.ts` types (adds `transaction` + `update(id).addTo`), eslint globals (`transaction`), and the README snippet. Verified on a multi-threaded local instance: increments 0 -> 1 -> 2 from an empty table; fresh scaffolds install/lint/tsc/build clean for both variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Next.js templates render on the server — the page is `force-dynamic` and reads Harper on every request — but the catalog entries had `ssr: false`, so the exported `create-harper/templates` contract reported the wrong capability and the picker omitted "+ SSR" (thanks @kriszyp). Set `ssr: true` for both entries. The `-ssr` name suffix is a Vite-variant convention that doesn't apply to Next.js (which is always server-rendered), so the `templates.test.js` invariant is relaxed to expect ssr=true for the nextjs framework and the suffix rule for the rest. The picker now shows the variants as "TypeScript + SSR" / "JavaScript + SSR". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ethan-Arrowood
left a comment
There was a problem hiding this comment.
Generally LGTM but I think we should fix the nextjs plugin first so you don't have to ship this with workarounds in place.
@harperfast/nextjs 2.2.2 fixed the multi-thread build race (serializes the build across worker threads — HarperFast/nextjs#52), which is the only reason these templates shipped `prebuilt: true` + build-first scripts. Revert to the same server-side-build model as the other templates: - config.yaml: drop `prebuilt: true` (Harper builds on `harper run`/deploy). - scripts: `dev` → `harper dev .`, `start` → `harper run .`, `deploy` → `harper deploy_component ...` (no `next build &&` prefix). - bump `@harperfast/nextjs` to `^2.2.3` so the build-serialization fix is required. - README deploy note back to "Harper builds on the server, no local build". Verified on Harper 5.1.22 with @harperfast/nextjs 2.2.3: a fresh scaffold builds and serves multi-threaded via `harper run` (no prebuilt, no race) and the counter works. (Server code still uses the injected `tables`/`transaction` globals, not `import 'harper'` — that stays until HarperFast/nextjs#41 + #51 land.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review: nextSmoke only GET'd `/`, so the write half of the counter (the `transaction` + `addTo` server action and its revalidation) was untested. It now invokes the increment action via its no-JS progressive-enhancement form POST (multipart with the rendered `$ACTION_ID_<hash>` field) and asserts a fresh reload shows the persisted count advanced by one. Also updated the header to reflect the server-side (not prebuilt) build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s#57/#58) Re-instates `prebuilt: true` + build-first scripts, reversing f8e4e35. That commit dropped prebuilt assuming @harperfast/nextjs 2.2.2's build-lock (#52) made the on-startup server build viable — but re-testing on a real cluster (stage, Harper 5.1.23) shows the server-side build still fails, regardless of Harper version or bundler: - Turbopack (Next 16 default): `next build` crashes the instant it runs inside a Harper worker thread — `uncaughtException: Worker creator already registered` → build aborts, no .next, app 404s/500s. Filed HarperFast/nextjs#57. - `bundler: webpack`: no crash, but a cold build overruns Harper's 30s `handleApplication` timeout → component load aborts. Filed HarperFast/nextjs#58. - `prebuilt: true`: works — verified live on stage (serves + atomic increment persists across the replicated cluster). Local `harper run`/`dev` and CI stay green because a plain-CLI `next build` (not inside a Harper worker thread) succeeds; only a multi-thread cluster hits the crash. - config.yaml: `prebuilt: true`; comment cites #57/#58 (not the fixed #52). - scripts: `dev`/`start`/`deploy` prefix `next build` again. - nextSmoke.js: build the app if `.next` is absent (harper run serves prebuilt, it won't build on startup); wording updated. Verified locally on 5.1.22. Catalog unchanged — studio: false stays (without an on-cluster build the plugin is a poor Studio experience). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Restored Re-tested the server-side-build model on a real cluster (stage, Harper 5.1.23). It fails regardless of bundler, so
Local Once HarperFast/nextjs#57 and #58 land we can revisit dropping prebuilt (and reconsider |
Follow-up to d1a5466 (prebuilt restore): the integration workflow comment still described the old "on-startup build" model. The Next.js smoke now serves the prebuilt output produced by the `build` step. Split into its own commit because pushing workflow files needs `workflow` OAuth scope (the HTTPS push used for d1a5466 rejects `.github/workflows/**`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🎉 This PR is included in version 1.11.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Adds two templates —
nextjs(JavaScript) andnextjs-ts(TypeScript) — that scaffold a Next.js App Router app running on Harper via@harperfast/nextjs. Because the app runs inside Harper, server actions and server components read and write tables directly through the injectedtablesglobal — no separate API server and no network round-trip.They slot in as a new Next.js framework family in the interactive picker and
--templateflag.What the templates are
A bare starter at the same altitude as the existing
vanilla/react/vuetemplates — not a full app. The only Harper touch is a counter persisted in aCounttable: a server component reads it, and a<form action>server action increments it. That shows the one thing unique to Next-on-Harper (server-sidetablesaccess) while staying minimal and safe — single known-key record (no scans), non-destructive (no delete), no user input (no validation surface).Catalog wiring:
lib/constants/templates.js+.d.ts,frameworks.js(new family + color),templates.test.js,package.jsonfiles, and the root README.Key decisions (a few are non-obvious — details below)
tablesglobal; do notimport 'harper'in server code. A top-levelimport 'harper'breaks the production build (Turbopack can't resolve it, and even under webpack it opens the DB during Next's build-time page-data collection). This matches the plugin's ownnext-16fixture.prebuilt: true+ build-first scripts). On a multi-threaded cluster the plugin's on-startup build races across worker threads and 404s (see below); shipping a prebuilt.nextavoids it. Verified on a stage cluster.graphqlSchema+ the plugin). The plugin owns HTTP routing, so there's nostatic/resthandler.studio: false. No Studio template package is built/published for these (new optional catalog flag → derivedstudioTemplateNames). Studio's edit-in-place model also doesn't fit Next.js (verified — see below).Validated on a real stage cluster
Root cause filed upstream as HarperFast/nextjs#52 (concurrent
next buildacross worker threads →ENOENTon.next/BUILD_ID), with@harperfast/vite'swithBuildLockas the fix to port. Also filed #51 (Turbopack can't resolveimport 'harper') and a repro on #37 (build vs. RocksDB lock).CI coverage
Both variants are in the
integration.yamlmatrix across npm / yarn / pnpm (scaffold → install → lint → format → build → runtime smoke). This caught real package-manager issues the npm-only path hid:typescriptmust be a devDependency even in the JS template (eslint-config-next→typescript-eslintrequires it; npm hoists it, yarn/pnpm don't).allowBuildsinpnpm-workspace.yaml(it removedonlyBuiltDependencies) orpnpm installfails on Next's native build-script deps.run testis now conditional (these templates ship no unit tests; existing templates unchanged).A dedicated
template.tests/nextSmoke.jsboots the app under Harper (multi-threaded) and asserts it serves/and renders the Harper-backed counter — since the Next.js apps route through the plugin and have no REST resource surface for the existingruntimeSmoke.js.Follow-ups (not blocking)
prebuilt: trueand let the server build on deploy — simplifying the scripts.studio: trueif/when the plugin works in Studio's deploy-only model; first-time publish perms + npm provenance for the two studio packages are also prerequisites.🤖 Generated with Claude Code