SEO Core
ExpressFastifyNext.jsServer-sideReal-time

Install Node.js Package

Install the SEO Core package, configure middleware and sync, and verify server-side deployment delivery in your Node.js application.

What it does

The @seo-core-app-ai/node-deploypackage integrates into your Node.js server as middleware. It maintains an up-to-date local copy of your active deployments, and applies them to every HTML response before it's sent to the client. Changes you make in the dashboard propagate to your server automatically — no app restart, no rebuild, no deployment pipeline required.

Server-side rendering

Deployments are applied in the HTML before it leaves your server — visible to crawlers and browsers without any JavaScript dependency.

Real-time push sync

Register your server's webhook URL and new deployments reach your app in under 3 seconds after you save them in the dashboard.

Automatic fallback polling

Even without a registered webhook, the package polls for changes every 30 seconds so deployments stay current regardless of your infrastructure.

Zero-restart updates

The deployment cache updates in the background. Your application keeps serving traffic throughout — no downtime, no restarts.

Requirements

  • Node.js 18 or later.
  • A SEO Core project with a verified domain and a deploy token.
  • Your server must be able to make outbound HTTPS requests to the SEO Core API.
  • For real-time push sync: your server must be publicly reachable via HTTPS.

Installation

  1. 1

    Install the package

    npm install @seo-core-app-ai/node-deploy
  2. 2

    Add your credentials as environment variables

    Find your Project ID and Deploy Token in your dashboard under Projects → Deploy Script → Node.js. Add them to your environment:

    SEO_DEPLOY_PROJECT_ID=your_project_id
    SEO_DEPLOY_TOKEN=your_deploy_token

    Important

    Never hard-code your deploy token in source files. Use environment variables so the token stays out of version control.

  3. 3

    Initialize the client at startup

    Create a singleton client — typically in a shared file like lib/seo-client.ts — and call initialize() once when your server starts:

    import { SeoDeployClient } from "@seo-core-app-ai/node-deploy";
    
    export const seoClient = new SeoDeployClient({
      projectId: parseInt(process.env.SEO_DEPLOY_PROJECT_ID!),
      token: process.env.SEO_DEPLOY_TOKEN!,
    });
    
    await seoClient.initialize();

    Tip

    Call initialize() before your server starts accepting requests so the deployment cache is populated from the first request.

Express integration

Add the middleware before your route handlers. It automatically intercepts HTML responses and applies any active deployments for the requested URL.

import express from "express";
import { createExpressMiddleware } from "@seo-core-app-ai/node-deploy/express";
import { seoClient } from "./lib/seo-client";

const app = express();

// Add before your routes
app.use(createExpressMiddleware(seoClient));

app.get("/", (req, res) => {
  res.send("<html><head><title>My Site</title></head>...</html>");
  // The middleware patches the title if a deployment is active for this URL
});

app.listen(3000);

Note

The middleware only modifies responses with a Content-Type: text/html header. JSON, images, and other response types are passed through unchanged.

Fastify integration

Register the plugin on your Fastify instance. It uses a lifecycle hook to patch HTML responses before they're sent.

import Fastify from "fastify";
import { seoDeployPlugin } from "@seo-core-app-ai/node-deploy/fastify";
import { seoClient } from "./lib/seo-client";

const app = Fastify();
await app.register(seoDeployPlugin, { client: seoClient });

app.get("/", async () => {
  return "<html><head><title>My Site</title></head>...</html>";
});

await app.listen({ port: 3000 });

Next.js integration (App Router)

The package ships a ready-made SeoDeployHead server component. Add it once to your root layout — no changes to individual pages required. It injects title, description, and canonical tags directly into the HTML on every request.

1. Create a singleton client

Create lib/seo-deploy-client.ts to hold a shared client instance that persists across hot reloads in development:

// lib/seo-deploy-client.ts
import { SeoDeployClient } from "@seo-core-app-ai/node-deploy";

declare global { var _seoDeployClient: SeoDeployClient | undefined; }

if (!global._seoDeployClient) {
  global._seoDeployClient = new SeoDeployClient({
    projectId: parseInt(process.env.SEO_DEPLOY_PROJECT_ID!),
    token: process.env.SEO_DEPLOY_TOKEN!,
  });
  global._seoDeployClient.initialize().catch(console.error);
}

export const seoDeployClient = global._seoDeployClient;

2. Pass the pathname via middleware

SeoDeployHead needs to know which page is being rendered. Add one line to your middleware.ts to forward the pathname as a request header:

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
  const res = NextResponse.next();
  res.headers.set("x-seo-core-pathname", req.nextUrl.pathname);
  return res;
}

export const config = { matcher: ["/((?!_next|favicon.ico).*)"] };

Note

If you already have a middleware.ts, just add the res.headers.set line — you don't need a separate middleware.

3. Add SeoDeployHead to your root layout

This is the only change needed to your app. Every page automatically gets deployments applied — no per-page code required.

// app/layout.tsx
import { SeoDeployHead } from "@seo-core-app-ai/node-deploy/nextjs/head";
import { seoDeployClient } from "@/lib/seo-deploy-client";

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        {/* Applies active deployments — title, description, canonical */}
        <SeoDeployHead client={seoDeployClient} />
      </head>
      <body>{children}</body>
    </html>
  );
}

Tip

SeoDeployHead returns null when no deployment is active for the current URL, so there is no performance cost on undeployed pages.

4. Set environment variables

SEO_DEPLOY_PROJECT_ID=your_project_id
SEO_DEPLOY_TOKEN=your_deploy_token

Important

Never commit your deploy token. Use environment variables.

Real-time push sync (optional)

By default, the client polls for deployment changes every 30 seconds. Push sync is optional — it delivers changes to your server the moment you save them in the dashboard (under 3 seconds), instead of waiting for the next poll.

Express and Fastify handle the push webhook automatically — no extra setup needed. Next.js users can optionally add a route handler:

// app/seo-core/sync/route.ts
import { seoDeployClient } from "@/lib/seo-deploy-client";
import { handleNextjsPushSync } from "@seo-core-app-ai/node-deploy/nextjs";

export async function POST(req: Request) {
  return handleNextjsPushSync(req, seoDeployClient);
}

Then register https://your-site.com/seo-core/sync as your webhook URL in Projects → Deploy Script → Node.js → Push sync webhook.

Note

If your server is behind a private network and push sync isn't reachable, the package falls back to polling automatically. You can adjust the interval with the pollIntervalMs option, or set it to 0 to disable polling entirely.

Configuration options

new SeoDeployClient({
  projectId: 123,               // Required. Your project ID.
  token: "your_token",          // Required. Keep in environment variables.
  apiOrigin: "https://www.seocoreapp.com/api", // Optional. Override API base URL.
  pollIntervalMs: 30_000,       // Optional. Background poll interval. Set 0 to disable.
  heartbeatIntervalMs: 60_000,  // Optional. Lightweight freshness check interval.
  webhookPath: "/seo-core/sync",// Optional. Path where your server receives push sync.
  publicUrl: "https://your-site.com", // Optional. Enables auto-registration of push sync webhook.
  deploymentTypes: ["title", "description"], // Optional. Limit which types apply.
  onError: (err) => console.error(err),      // Optional. Error callback.
});

Serverless environments

Important

Serverless functions (Vercel Functions, AWS Lambda) don't maintain persistent memory between invocations. The client fetches deployments on each cold start and reuses them for warm invocations within the same function lifetime. Set pollIntervalMs: 0 to avoid unnecessary background timers in serverless contexts.

For shared state across multiple serverless invocations, the client supports a custom cacheAdapter interface that you can back with Redis, Upstash, or any key-value store.