Skip to content

Latest commit

 

History

404 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tnl

tnl gives your local app an end-to-end encrypted HTTPS URL. By default, every Git worktree gets its own unique URL, so you can easily preview multiple development tracks in parallel.

                           browser
                              |
                            HTTPS
                              v
                 tnld (hosted or self-hosted)
                              |
                      encrypted traffic
                              |
+-----------------------------v-----------------------------+
|                       your machine                        |
|                                                           |
|  main repo                    perf worktree               |
|  web URL -> :5173             web URL -> :5174            |
|  api URL -> :3001             api URL -> :3002            |
|                                                           |
+-----------------------------------------------------------+

start developing

First, install the NPM package:

npm install -D @tnldotdev/tnl@next

Then, initialize with the helper:

npx tnl init

tnl init walks you through creating a tnl.config.ts with the command that starts your app. For example, in a Vite project:

// tnl.config.ts
import { defineConfig } from "@tnldotdev/tnl/config";

export default defineConfig({
  services: {
    app: {
      directory: ".",
      dev: { command: ["vite", "dev"] },
    },
  },
});

Then run:

npx tnl dev

Sign in to GitHub when prompted. Your app will start on the next available port, and tnl publishes to its unique URL, with only your current IP whitelisted:

+--[ tnl dev ]-- ready ----------------------------------------+
|                                                              |
|  https://app-example-abcd1234.ecstatic-penguin.tnl.dev       |
|     |                                                        |
|     v                                                        |
|    tnl                                                       |
|     |                                                        |
|     v                                                        |
|  http://127.0.0.1:5173                                       |
|                                                              |
|  framework                 vite                              |
|  automatically allowed IP  122.151.3.101                     |
|                                                              |
+-- ctrl+c to stop --------------------------------------------+

Hot reloading works automatically, as well as SSE/websockets/streaming responses.

You can allow reviewers or webhooks when you need them. For example, tnl dev --allow-provider stripe allows Stripe's published webhook IPs alongside your current IP.

Follow the quickstart or read about framework setup.

give your services their own urls

If your project has multiple separate services, you can put both startup commands in tnl.config.ts:

// tnl.config.ts
import { defineConfig } from "@tnldotdev/tnl/config";

export default defineConfig({
  services: {
    web: {
      directory: "apps/web",
      dev: { command: ["vite", "dev"] },
    },
    api: {
      directory: "apps/api",
      dev: { command: ["pnpm", "dev"] },
    },
  },
});

Start both services either together or individually:

npx tnl dev
# or
npx tnl dev web
npx tnl dev api

These will have separate URLs:

service URL
web https://web-example-k7n2p9.ecstatic-penguin.tnl.dev
api https://api-example-k7n2p9.ecstatic-penguin.tnl.dev

Use the API URL assigned to this project in the frontend:

import { tnl } from "@tnldotdev/tnl";

const apiURL = tnl.services?.api?.url;
// https://api-example-k7n2p9.ecstatic-penguin.tnl.dev

Read about project configuration and framework metadata. For a Node or Bun API, use tnl.port and tnl.register(server) so worktrees can listen on different ports. See API server setup.

open changes side by side

Create another Git worktree. The project configuration comes with it:

git worktree add -b perf ../perf

Then start the two services:

npx tnl dev
# or
npx tnl dev web
npx tnl dev api

The same frontend is now running from two checkouts:

checkout frontend public URL
primary https://web-example-k7n2p9.ecstatic-penguin.tnl.dev
../perf https://web-example-perf-m4q8s2.ecstatic-penguin.tnl.dev

tnl names each URL after the service and project, adding the directory name for a linked worktree. A short ID keeps names distinct, and switching branches leaves the URL unchanged.

For Next.js and Vite, there is no port bookkeeping. If another worktree already uses the preferred port, the framework can choose another and tnl follows the listener it actually opens. Restarting the same worktree reuses its URL.

See what is running across your local worktrees from any of them:

npx tnl status --all

custom domain

Claim a development subdomain and make it the default for new public URLs:

npx tnl domain claim dev.example.com --default
npx tnl domain list

Delegate the subdomain with the NS records. Then, dev URLs will look like:

https://web-example-k7n2p9.chase.dev.example.com

Use a custom domain.

self-host

After deploying the tnl server, update your config with it:

export default defineConfig({
  server: "https://control.example.com",
  services: {
    // ...
  },
});

The same config works with hosted tnl.dev or your own server.

Run your own server.

not using typescript/javascript, or don't want config?

You can use Homebrew to install the tnl client. Publish an HTTP app already listening on port 3000 without any configuration:

brew install tnldotdev/tap/tnl
tnl publish 3000

This works very similarly to tnl dev. Read about tnl publish.

how much does it cost?

tnl.dev is free for now. Teams have a soft limit of 50 GiB of transfer per month across their public URLs, counting traffic in both directions.

Accounts and teams created while it is free will keep a free plan if paid options arrive.

license

The client and server are MIT-licensed. See the dependency notices, browse the command reference, or contribute.

Releases

Packages

Used by

Contributors

Languages