tnl.dev :: docs

next.js.

connect Next.js to tnl dev from the consolidated tnl package.

The Next.js integration is part of @tnldotdev/tnl. It preserves the project's Next.js configuration and reports the actual post-bind development target to tnl dev. During ordinary development it exposes generated project metadata without changing networking; builds remain inert.

install and initialize

Next.js 16.3.4 or newer and Node.js 22.18 or newer are required. Install one package:

install tnl
npm install --save-dev @tnldotdev/tnl@next
npm exec -- tnl init

wrap the next.js config

Pass the existing configuration to withTnl:

next.config.ts
import { withTnl } from "@tnldotdev/tnl/next";
 
export default withTnl({
  reactStrictMode: true,
});

The wrapper also accepts promised configuration and synchronous or asynchronous configuration functions. Existing options are preserved. withTnl accepts only the Next.js configuration value, not route options.

Keep the selected team, named services, hostname, visitor policy, and development commands in project configuration.

configure a named service

tnl init can create the service entry. The TypeScript form is:

tnl.config.ts
export default {
  team: "Acme Engineering",
  services: {
    web: {
      directory: ".",
      dev: { command: ["next", "dev"] },
    },
  },
};

No explicit hostname is needed for normal development. tnl derives it from the Git worktree and the web service name, after applying command, environment, and project-config precedence.

Start the public workflow with the project-local client:

run Next.js through tnl
npm exec -- tnl dev

If no saved hosted session is usable, tnl opens browser authentication automatically and resumes the same command after sign-in. Ordinary npm run dev remains local; the wrapper only injects previously generated project metadata.

how port discovery works

Next.js keeps its default or explicitly configured host and port behavior. The integration reads the actual listener target after Next.js binds and registers that target with tnl. This post-bind step is important when a preferred port is occupied: tnl publishes the port Next.js actually chose instead of an unrelated process still listening on the preferred port. The assigned public hostname is added to allowedDevOrigins without replacing project values.

No explicit Next.js hostname is required. A project can still set --hostname/-H to expose the development server on the LAN or choose another binding; that listener choice is preserved and is independent from whether tnl publishes the service.

To require one port for an ad hoc run, pass it to tnl:

require port 3000
tnl dev --port 3000 -- npm run dev

tnl dev supplies PORT to the child and treats it as exact. If Next.js reports a different post-bind port, startup fails with TNL_TARGET_MISMATCH instead of publishing the wrong local process.

project runtime

Server and client application code read public project metadata from the optional root export:

app/project.ts
import { tnl } from "@tnldotdev/tnl";
 
tnl?.memberNamespace;
tnl?.services.web.hostname;
tnl?.services.web.url;
tnl?.runningUnderTnlDev;

Before tnl dev starts the child, tnl writes .tnl/project.json and .tnl/project.d.ts. Include .tnl/project.d.ts in the application's TypeScript inputs. It augments tnl.services with exact service keys and literal member-namespace, hostname, and HTTPS URL values from the selected team and routes.

During tnl dev, runningUnderTnlDev is true. During ordinary npm run dev, the wrapper can read existing generated metadata and sets it to false without changing Next.js networking. The export is undefined when metadata is absent and during builds and previews. tnl publish cannot inject metadata into an already-running application process. No saved authentication or server access token is exposed.

troubleshooting

  • the integration did not connect: confirm the import is exactly @tnldotdev/tnl/next, run tnl dev rather than the framework command alone, and rerun tnl init to report any integration action still needed.
  • Next.js selected another port: omit tnl dev --port to allow normal discovery, or make the configured and forced ports agree. tnl always uses the actual post-bind target.
  • the public origin is rejected: keep withTnl around the exported config; it adds the current assigned hostname without replacing intentional allowedDevOrigins values.
  • the route stays in provisioning: use tnl status for the service state and follow the diagnostic's contextual help URL rather than changing Next.js networking settings.
  • visitors are denied: the current-IP policy is working. Add a narrow allow-list entry or use tnl dev --public intentionally.

Use development workflow for route reconciliation, ephemeral routes, and aggregate denied visitor warnings.