Next.js Plugin (@harperfast/nextjs)
@harperfast/nextjs is a Harper Plugin for running Next.js applications directly on Harper. It supports Next.js v14, v15, and v16.
Source: github.com/HarperFast/nextjs — Example app
Setup
1. Install
npm install @harperfast/nextjs
2. Wrap your Next.js config
Use withHarper() to wrap your existing Next.js config. All config formats are supported:
// next.config.js (CommonJS)
const { withHarper } = require('@harperfast/nextjs');
module.exports = withHarper({
// your existing Next.js config
});
// next.config.mjs (ESM)
import { withHarper } from '@harperfast/nextjs';
export default withHarper({
// your existing Next.js config
});
// 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
'@harperfast/nextjs':
package: '@harperfast/nextjs'
4. Run
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 addedharper(orharper-pro) to theserverExternalPackageslist in your Next.js config.
// 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;
}
// 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 (
<section>
<h1>{dog.name}</h1>
<p>Breed: {dog.get('breed')}</p>
</section>
);
}
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)
'@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), the Data Cache, and 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:
// next.config.js (CommonJS)
const { withHarper, cacheHandlerPath } = require('@harperfast/nextjs');
module.exports = withHarper({
cacheHandler: cacheHandlerPath(__dirname),
});
// next.config.mjs (ESM)
import { withHarper, cacheHandlerPath } from '@harperfast/nextjs';
export default withHarper({
cacheHandler: cacheHandlerPath(import.meta.dirname),
});
// next.config.ts (TypeScript)
import { withHarper, cacheHandlerPath } from '@harperfast/nextjs';
export default withHarper({
cacheHandler: cacheHandlerPath(import.meta.dirname),
});
Tag Invalidation
revalidateTag() is supported and propagates across the cluster automatically. A typical flow:
// 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 <h1>{product.name}</h1>;
}
// 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:
revalidateTag(tag)writes a{ tag, timestamp }row to thenextjs_cache_invalidationtable and updates an in-memory map in the calling worker.- Every other Harper worker subscribes to that table and updates its own map when the row is replicated - typically within milliseconds.
- On the next
cache.get(), if any of the cached entry's tags has an invalidation timestamp newer than the entry'slastModified, the handler returnsnulland 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. |