Skip to content

Instantly share code, notes, and snippets.

@asika32764
Last active November 18, 2025 15:44
Show Gist options
  • Select an option

  • Save asika32764/1dc7fa9acb4dac6e31770b3e393ca972 to your computer and use it in GitHub Desktop.

Select an option

Save asika32764/1dc7fa9acb4dac6e31770b3e393ca972 to your computer and use it in GitHub Desktop.
Vite plugin to run hookdeck with Nuxt / Vite dev server concurrently

Register to config:

    plugins: [
      hookdeck({
        source: import.meta.env.NUXT_HOOKDECK_SOURCE || '{your hookdeck source}',
        urlMap: {
          Clerk: 'hook/clerk',
          Paddle: 'hook/paddle',
        },
        enabled: import.meta.env.NUXT_HOOKDECK_ENABLED === '1',
      }),
    ],

The display messages ehen running dev server:

Connections
xxx -> xxx forwarding to /api/
                                                                                                                                                                                            4:07:08 PM
HOOKDECK URL MAPPING:                                                                                                                                                                       4:07:08 PM
┌─────────┬──────────┬──────────────────────────────────────────────────┬─────────────────────────────────────────────────┐                                                                 4:07:08 PM
│ (index) │ Name     │ Hook                                             │ Dest                                            │
├─────────┼──────────┼──────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
│ 0       │ 'Clerk'  │ 'https://hkdk.events/xxxxxxxxxxxx/hook/clerk'  │ 'http://localhost:3000/api/hook/clerk'  │
│ 1       │ 'Paddle' │ 'https://hkdk.events/xxxxxxxxxxxx/hook/paddle' │ 'http://localhost:3000/api/hook/paddle' │
└─────────┴──────────┴──────────────────────────────────────────────────┴─────────────────────────────────────────────────┘
✔ Nuxt Nitro server built in 614ms                                                                                                                                                   nitro 4:07:08 PM
ℹ Vite server warmed up in 1ms                                                                                                                                                             4:07:08 PM
ℹ Vite client warmed up in 1ms

Blog post: https://lab.simular.co/concurrently-run-hookdeck-with-nuxt-vite-app

import { spawn } from 'node:child_process';
import fs from 'node:fs';
import { type Plugin } from 'vite';
export interface HookdeckOptions {
hookdeckBinary?: string;
source?: string;
port?: number;
pidFile?: string;
urlMap?: Record<string, string>;
enabled?: boolean;
}
export default function hookdeck(options: HookdeckOptions = {}): Plugin {
return {
name: 'hookdeck-concurrently',
async configureServer(server) {
const enabled = options.enabled ?? process.env.NUXT_HOOKDECK_ENABLED === '1';
if (!enabled) {
return;
}
const port = (server.httpServer?.address() as any)?.port ?? options.port ?? 3000;
// NOTE: In vite, you must create a tmp folder and exclude it by .gitignore
const pidFile = options.pidFile ?? `./.nuxt/hookdeck.${port}.pid`;
const source = options.source || '';
const hookdeckBinary = options.hookdeckBinary || 'hookdeck';
if (!source) {
console.warn('[vite hookdeck] No source provided, skipping hookdeck setup.');
return;
}
if (fs.existsSync(pidFile)) {
const oldPid = fs.readFileSync(pidFile, { encoding: 'utf-8' });
try {
process.kill(Number(oldPid), 0);
console.log(`[vite hookdeck] Hookdeck is already running with PID: ${oldPid}`);
return;
} catch (e) {
console.log(`[vite hookdeck] Old hookdeck process not found, starting a new one...`);
}
}
console.log(`[vite hookdeck] Starting hookdeck on port ${port}`)
const child = spawn(hookdeckBinary, ['listen', String(port), source], {
shell: true, // Windows must add this
});
child.stdout.on('data', function (data) {
console.log(data.toString().trim());
const urlMap = options.urlMap;
const matchProxy = data.toString().match(/URL: https:\/\/(hkdk\.events\/([a-zA-Z0-9]+))/);
const matchDest = data.toString().match(/forwarding to ([a-zA-Z0-9_\-\/\:]+)/);
if (urlMap && matchProxy && matchDest) {
const url = matchProxy[1];
const path = matchDest[1];
const data: Record<string, string>[] = [];
for (const [key, value] of Object.entries(urlMap)) {
const endpoint = 'https://' + `${url}/${value}`.replace(/\/\//g, '/');
const dest = 'http://' + `localhost:${port}${path}/${value}`.replace(/\/\//g, '/');
// console.log(` [${key}] ${endpoint} => ${dest}`);
data.push({
Name: key,
Hook: endpoint,
Dest: dest
});
}
console.log('');
console.log('HOOKDECK URL MAPPING:');
console.table(data);
}
});
fs.writeFileSync(pidFile, child.pid?.toString() || '', { encoding: 'utf-8' });
child.on('error', (err) => {
console.error('[vite hookdeck error]', err)
})
let exit = () => {
console.log('[vite hookdeck] Shutting down hookdeck...')
child?.kill('SIGTERM')
if (fs.existsSync(pidFile)) {
fs.unlinkSync(pidFile);
}
};
server.httpServer?.on('close', exit);
process.on('SIGINT', exit);
process.on('SIGTERM', exit);
process.on('exit', exit);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment