diff --git a/reference/components/extension-api.md b/reference/components/extension-api.md index 2d02098b..7104deeb 100644 --- a/reference/components/extension-api.md +++ b/reference/components/extension-api.md @@ -151,13 +151,13 @@ In addition to the `files`, `urlPath`, and `package` options, Protocol Extension Many protocol extensions accept `port` and `securePort` options for configuring networking handlers. -Example using `@harperdb/nextjs`: +Example Protocol Extension configuration: ```yaml -'@harperdb/nextjs': - package: '@harperdb/nextjs' +'@my-org/my-protocol-extension': + package: '@my-org/my-protocol-extension' files: './' - prebuilt: true + port: 9000 dev: false ``` diff --git a/reference/components/nextjs.md b/reference/components/nextjs.md new file mode 100644 index 00000000..f67c2f2d --- /dev/null +++ b/reference/components/nextjs.md @@ -0,0 +1,278 @@ +--- +title: Next.js Plugin +--- + +# Next.js Plugin (`@harperfast/nextjs`) + +`@harperfast/nextjs` is a Harper [Plugin](./plugin-api.md) for running [Next.js](https://nextjs.org/) applications directly on Harper. It supports Next.js v14, v15, and v16. + +Source: [github.com/HarperFast/nextjs](https://github.com/HarperFast/nextjs) — [Example app](https://github.com/HarperFast/nextjs-example) + +## Setup + +### 1. Install + +```sh +npm install @harperfast/nextjs +``` + +### 2. Wrap your Next.js config + +Use `withHarper()` to wrap your existing Next.js config. All config formats are supported: + +```js +// next.config.js (CommonJS) +const { withHarper } = require('@harperfast/nextjs'); + +module.exports = withHarper({ + // your existing Next.js config +}); +``` + +```js +// next.config.mjs (ESM) +import { withHarper } from '@harperfast/nextjs'; + +export default withHarper({ + // your existing Next.js config +}); +``` + +```ts +// next.config.ts (TypeScript) +import { withHarper } from '@harperfast/nextjs'; + +export default withHarper({ + // your existing Next.js config +}); +``` + +`withHarper()` automatically adds `harper` and `harper-pro` to `serverExternalPackages` so Harper's native dependencies are handled correctly by the Next.js bundler. + +### 3. Configure in `config.yaml` + +```yaml +'@harperfast/nextjs': + package: '@harperfast/nextjs' +``` + +### 4. Run + +```sh +harper run nextjs-app +``` + +## Using Harper Globals + +Within any server-side code path, import `harper` to access Harper's global APIs and interact with Harper tables directly: + +> Ensure you are using `withHarper()`, or have manually added `harper` (or `harper-pro`) to the `serverExternalPackages` list in your Next.js config. + +```js +// app/actions.js +'use server'; + +import 'harper'; + +export async function listDogs() { + const dogs = []; + for await (const dog of tables.Dog.search()) { + dogs.push({ id: dog.id, name: dog.name }); + } + return dogs; +} +``` + +```jsx +// app/dogs/[id]/page.jsx +import { getDog, listDogs } from '@/app/actions'; + +export async function generateStaticParams() { + const dogs = await listDogs(); + return dogs; +} + +export default async function Dog({ params }) { + const dog = await getDog(params.id); + + return ( +
+

{dog.name}

+

Breed: {dog.get('breed')}

+
+ ); +} +``` + +## Options + +All options are configured in `config.yaml` under the `@harperfast/nextjs` key. All options are optional. + +### `bundler` + +Type: `'webpack' | 'turbopack'` + +Selects the bundler used for building and serving the Next.js application. The default depends on the Next.js version: + +- Next.js v16: defaults to `turbopack` (matching the Next.js v16 default) +- Next.js v15: defaults to `webpack` (matching the Next.js v15 default) +- Next.js v14: always `webpack` (Turbopack is not supported) + +```yaml +'@harperfast/nextjs': + package: '@harperfast/nextjs' + bundler: webpack +``` + +### `dev` + +Type: `boolean` + +Default: `false` + +Enables Next.js development mode with hot module replacement (HMR). + +> Dev mode relies on WebSockets. If you encounter an `Invalid WebSocket frame:` error, disable other WebSocket services running on the same port. + +### `prebuilt` + +Type: `boolean` + +Default: `false` + +When `true`, the plugin looks for an existing `.next` directory and skips the build step. Useful when the app is pre-built before deployment. + +### `buildOnly` + +Type: `boolean` + +Default: `false` + +Builds the Next.js application and then exits, including shutting down Harper. Useful as a CI build step. + +### `port` + +Type: `number` + +Default: Harper default port (`9926`) + +Custom HTTP port for the Next.js server. + +### `securePort` + +Type: `number` + +Default: Harper default secure port + +Custom HTTPS port for the Next.js server. + +### `runFirst` + +Type: `boolean` + +Default: `false` + +When `true`, the Next.js request handler runs before any other Harper HTTP middleware. Useful for scenarios where Next.js handles authentication directly. + +Note: enabling `runFirst` conflicts with Harper's REST API on the same port. Use a dedicated `port` to avoid conflicts. + +## Caching + +`@harperfast/nextjs` includes a Harper-backed cache handler for Next.js [Incremental Static Regeneration (ISR)](https://nextjs.org/docs/app/guides/incremental-static-regeneration), the [Data Cache](https://nextjs.org/docs/app/deep-dive/caching#data-cache), and [`unstable_cache`](https://nextjs.org/docs/app/api-reference/functions/unstable_cache). + +Cached entries live in Harper instead of the worker's local filesystem, so a cache write on one cluster node is immediately visible to every other node. + +> **Note:** The cache handler is currently a work in progress. + +### Enabling + +Use the `cacheHandlerPath()` helper to set the `cacheHandler` path. This helper resolves the path relative to your config file, which is required for Turbopack compatibility: + +```js +// next.config.js (CommonJS) +const { withHarper, cacheHandlerPath } = require('@harperfast/nextjs'); + +module.exports = withHarper({ + cacheHandler: cacheHandlerPath(__dirname), +}); +``` + +```js +// next.config.mjs (ESM) +import { withHarper, cacheHandlerPath } from '@harperfast/nextjs'; + +export default withHarper({ + cacheHandler: cacheHandlerPath(import.meta.dirname), +}); +``` + +```ts +// next.config.ts (TypeScript) +import { withHarper, cacheHandlerPath } from '@harperfast/nextjs'; + +export default withHarper({ + cacheHandler: cacheHandlerPath(import.meta.dirname), +}); +``` + +### Tag Invalidation + +[`revalidateTag()`](https://nextjs.org/docs/app/api-reference/functions/revalidateTag) is supported and propagates across the cluster automatically. A typical flow: + +```js +// app/products/[id]/page.js +import { unstable_cache } from 'next/cache'; + +const getProduct = unstable_cache( + async (id) => { + const res = await fetch(`https://api.example.com/products/${id}`); + return res.json(); + }, + ['product'], + { tags: ['products'], revalidate: 3600 } +); + +export default async function ProductPage({ params }) { + const product = await getProduct(params.id); + return

{product.name}

; +} +``` + +```js +// app/api/revalidate/route.js +import { revalidateTag } from 'next/cache'; +import { NextResponse } from 'next/server'; + +export async function POST(request) { + const tag = new URL(request.url).searchParams.get('tag'); + revalidateTag(tag); + return NextResponse.json({ revalidated: true }); +} +``` + +`fetch()` calls with `next: { tags: [...] }` and the `'use cache'` directive (with `cacheTag()`) are also supported - anywhere Next.js attaches tags to a cached value, the handler picks them up. + +### How Invalidation Works + +The cache handler uses a soft-invalidation model: + +1. `revalidateTag(tag)` writes a `{ tag, timestamp }` row to the `nextjs_cache_invalidation` table and updates an in-memory map in the calling worker. +2. Every other Harper worker subscribes to that table and updates its own map when the row is replicated - typically within milliseconds. +3. On the next `cache.get()`, if any of the cached entry's tags has an invalidation timestamp newer than the entry's `lastModified`, the handler returns `null` and Next.js regenerates the entry. + +The `nextjs_cache_invalidation` rows expire after 7 days so abandoned tags do not accumulate indefinitely. + +### Cache Tables + +Enabling the cache handler adds two tables to the `harperfast_nextjs` database: + +| Table | Purpose | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `nextjs_isr_cache` | One row per cached entry. Stores `data` (the Next.js `IncrementalCacheValue`), `tags`, and `lastModified`. | +| `nextjs_cache_invalidation` | One row per invalidated tag. `id` is the tag; `timestamp` is when `revalidateTag` was called. Auto-expires after 7 days. | + +## See Also + +- [Components Overview](./overview.md) +- [Plugin API](./plugin-api.md) +- [Next.js Example App](https://github.com/HarperFast/nextjs-example) diff --git a/reference/components/overview.md b/reference/components/overview.md index 2f0e5096..d680d2a3 100644 --- a/reference/components/overview.md +++ b/reference/components/overview.md @@ -52,12 +52,13 @@ The relationship between applications, extensions, and Harper core: ``` Applications - ├── Next.js App → @harperdb/nextjs extension + ├── Next.js App → @harperfast/nextjs plugin ├── Apollo App → @harperdb/apollo extension - └── Custom Resource → jsResource + graphqlSchema + rest extensions + └── Custom Resource → jsResource + graphqlSchema + rest plugins -Extensions - ├── Custom: @harperdb/nextjs, @harperdb/apollo, @harperdb/astro +Plugins / Extensions + ├── Custom Plugins: @harperfast/nextjs + ├── Custom Extensions: @harperdb/apollo, @harperdb/astro └── Built-In: graphqlSchema, jsResource, rest, static, loadEnv, ... Core @@ -102,15 +103,14 @@ Any custom component must be configured with a `package` option for Harper to lo ```json { "dependencies": { - "@harperdb/nextjs": "1.0.0" + "@harperfast/nextjs": "2.0.0" } } ``` ```yaml -'@harperdb/nextjs': - package: '@harperdb/nextjs' - files: './' +'@harperfast/nextjs': + package: '@harperfast/nextjs' ``` The `package` value supports any valid npm dependency specifier: npm packages, GitHub repos, tarballs, local paths, and URLs. This is because Harper generates a `package.json` from component configurations and uses `npm install` to resolve them. @@ -141,14 +141,17 @@ Extensions require an `extensionModule` option pointing to the extension source. - [`@harperdb/prometheus-exporter`](https://github.com/HarperDB/prometheus-exporter) - [`@harperdb/acl-connect`](https://github.com/HarperDB/acl-connect) -### Extensions +### Plugins -- [`@harperdb/nextjs`](https://github.com/HarperDB/nextjs) — Run a Next.js application on Harper -- [`@harperdb/apollo`](https://github.com/HarperDB/apollo) — Serve an Apollo GraphQL server backed by Harper -- [`@harperdb/astro`](https://github.com/HarperDB/astro) — Run an Astro application on Harper +- [`@harperfast/nextjs`](https://github.com/HarperFast/nextjs) — Run a Next.js application on Harper ([docs](./nextjs.md)) - [`@harperfast/vite-plugin`](https://github.com/HarperFast/vite-plugin) — Develop and serve Vite-built front-ends from Harper, with HMR via `harper run` -Each plugin owns its own documentation in its repository — refer to the linked README for installation, configuration, and version-specific details. +### Extensions + +- [`@harperdb/apollo`](https://github.com/HarperFast/apollo) — Serve an Apollo GraphQL server backed by Harper +- [`@harperdb/astro`](https://github.com/HarperFast/astro) — Run an Astro application on Harper + +Each component owns its own documentation in its repository — refer to the linked README for installation, configuration, and version-specific details. ## Component Status Monitoring diff --git a/reference/static-files/overview.md b/reference/static-files/overview.md index 7104e0c6..ee255067 100644 --- a/reference/static-files/overview.md +++ b/reference/static-files/overview.md @@ -169,6 +169,12 @@ static: A request to any unmatched path returns `static/index.html` with a `200` status code, allowing the client-side router to handle navigation. +## Dynamic Applications + +The `static` plugin handles pure static file hosting. If you need server-side rendering, API routes, or other dynamic behavior, consider using a plugin designed for that purpose: + +- [`@harperfast/nextjs`](../components/nextjs.md) - Run a full Next.js application (SSR, ISR, API routes) directly on Harper + ## Related - [Components Overview](../components/overview.md) diff --git a/sidebarsReference.ts b/sidebarsReference.ts index c7457f84..db02b8fb 100644 --- a/sidebarsReference.ts +++ b/sidebarsReference.ts @@ -120,6 +120,11 @@ const sidebars: SidebarsConfig = { id: 'components/javascript-environment', label: 'JavaScript Environment', }, + { + type: 'doc', + id: 'components/nextjs', + label: 'Next.js Plugin', + }, ], }, {