@safeki/nuxt

My new Nuxt module
2,922
Install
npm install @safeki/nuxt
Latest Version:0.1.14
Vue:3
License:MIT
Last Updated:Sep 21, 2026
Links: npm
Maintainer: jetmoon

@safeki/nuxt

npm version npm downloads License Nuxt

Safekit utilities for Nuxt applications. The module provides Sentry setup helpers, server-side crypto helpers, and a private Slack message API for Nitro runtime code.

Features

  • Nuxt module wrapper for @sentry/nuxt
  • sentry.info() helper for sending info-level Sentry messages from client or server code
  • Private runtime config storage for a Safekit crypto secret key
  • AES-256-GCM encryption and decryption for encrypted Safekit values
  • Per-payload random salt and scrypt key derivation for new encrypted values
  • Server auto-import for decrypting encrypted Safekit values in Nitro code
  • Config-time decryption helper for loading private runtime config values
  • Server-only Slack postMessage() auto-import initialized from runtime environment variables
  • No public API endpoint is registered for crypto operations

Quick Setup

Install the module in your Nuxt application:

npx nuxt module add @safeki/nuxt

Add the module in nuxt.config.ts. Slack module options are optional build-time fallbacks; production credentials are loaded when the server starts:

export default defineNuxtConfig({
  modules: [
    ['@safeki/nuxt', {
      crypto: {
        secretKey: process.env.SAFEKIT_CRYPTO_SECRET_KEY,
      },
      slack: {
        // Optional build-time fallback values.
        token: '',
        channel: '',
      },
      sentry: {
        telemetry: false,
        sourcemaps: {
          disable: true,
        },
        client: {
          dsn: process.env.NUXT_PUBLIC_SENTRY_DSN,
          tracesSampleRate: 1,
          replaysSessionSampleRate: 0,
          replaysOnErrorSampleRate: 1,
        },
      },
    }],
  ],
})

Crypto

New Safekit encrypted payloads use the v2 format:

safekit:v2.<salt>.<iv>.<authTag>.<encryptedText>

encrypt() derives a 256-bit AES key with scrypt using a random salt for every payload, then encrypts with AES-256-GCM and authenticates the payload header as additional data. decrypt() still supports legacy safekit:v1.<iv>.<authTag>.<encryptedText> values for backward compatibility.

Server Decryption

Use decryptSafekit() in server routes, server middleware, server plugins, and Nitro code. The module registers it as a server-only auto-import, so most Nuxt server files can call it directly:

export default defineEventHandler((event) => {
  const encryptedText = getQuery(event).value

  if (typeof encryptedText !== 'string') {
    throw createError({
      statusCode: 400,
      statusMessage: 'Missing encrypted value',
    })
  }

  return {
    value: decryptSafekit(encryptedText),
  }
})

If you prefer explicit imports, import it from Nuxt's generated imports:

import { decryptSafekit } from '#imports'

export default defineEventHandler(() => {
  return decryptSafekit('safekit:v2...')
})

By default, decryptSafekit() reads nuxtSafekit.crypto.secretKey from private runtime config. Configure that key in nuxt.config.ts:

export default defineNuxtConfig({
  modules: [
    ['@safeki/nuxt', {
      crypto: {
        secretKey: process.env.SAFEKIT_CRYPTO_SECRET_KEY,
      },
    }],
  ],
})

You can also pass the secret key as the second argument. This is useful when the key is resolved per request or you do not want to configure crypto.secretKey globally:

export default defineEventHandler((event) => {
  const encryptedText = getRouterParam(event, 'value')
  const secretKey = getHeader(event, 'x-safekit-secret-key')

  if (!encryptedText || !secretKey) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Missing encrypted value or secret key',
    })
  }

  return {
    value: decryptSafekit(encryptedText, secretKey),
  }
})

Do not expose this through a client-side composable or public API unless your application adds its own authorization and validation. The helper is intended for trusted server runtime code.

Config-Time Decryption

Use the package entry export when decrypting values while nuxt.config.ts is loading. At this point the Nuxt runtime context does not exist yet, so pass the secret key or a runtime config-shaped object explicitly:

import nuxtSafekit, {
  decryptedSafekit,
  type SafekitCryptoRuntimeConfig,
} from '@safeki/nuxt'

const secretKey = process.env.SAFEKIT_CRYPTO_SECRET_KEY || ''
const encryptedApiToken = process.env.SAFEKIT_ENCRYPTED_API_TOKEN || ''
const safekitRuntimeConfig = {
  nuxtSafekit: {
    crypto: {},
  },
} satisfies SafekitCryptoRuntimeConfig

export default defineNuxtConfig({
  runtimeConfig: {
    ...safekitRuntimeConfig,
    apiToken: encryptedApiToken
      ? decryptedSafekit(encryptedApiToken, safekitRuntimeConfig, secretKey)
      : '',
  },
  modules: [
    [nuxtSafekit, {
      crypto: {
        secretKey,
      },
    }],
  ],
})

secretKey is optional on the runtime config-shaped object. You may put it under nuxtSafekit.crypto.secretKey, pass it as the third argument, or pass both values together as { runtimeConfig, secretKey }.

The correctly spelled decryptSafekit() export is also available for config-time use. decryptedSafekit() is provided as a readability alias, and decrypedSafekit() is kept for existing integrations that already use the typo alias.

API Naming

Use decryptSafekit() as the primary server API.

The lower-level crypto implementation has a decrypt(encryptedText, secretKey) function internally because it always requires the caller to pass the key directly. The Nuxt server helper is intentionally named decryptSafekit(encryptedText, secretKey?) because it can either read the private runtime config key for you or use the explicit second argument.

decrypted(), decryptedSafekit(), and the typo alias decrypedSafekit() remain available for backward compatibility. New runtime code should use decryptSafekit().

Slack

The server plugin initializes Slack when Nitro starts. Set SLACK_BOT_TOKEN and SLACK_CHANNEL on the process that runs your deployed Nuxt server; they do not need to be available while the application is built. These values are private and are never exposed to client code.

You may add Slack module options as build-time fallback values, such as for local development:

export default defineNuxtConfig({
  modules: [
    ['@safeki/nuxt', {
      slack: {
        token: '',
        channel: '',
      },
    }],
  ],
})

For example, configure the deployment runtime environment:

SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_CHANNEL=C0123456789

SLACK_BOT_TOKEN and SLACK_CHANNEL take precedence over nuxtSafekit.slack values. If neither is set, the module preserves the existing nuxtSafekit.slack runtime-config fallback, including values supplied through Nuxt's NUXT_NUXT_SAFEKIT_SLACK_TOKEN and NUXT_NUXT_SAFEKIT_SLACK_CHANNEL variables.

Call postMessage() only from Nitro server code. The module registers it as a server-only auto-import and sends through Slack's chat.postMessage API:

export default defineEventHandler(async () => {
  return await postMessage({
    text: 'Deploy completed',
  })
})

Pass channel to override the configured default for one message:

await postMessage({
  channel: 'C0123456789',
  text: 'An incident needs attention',
})

postMessage() requires a configured token, a channel, and at least one of text, blocks, or attachments. It throws an error when Slack rejects a message. Do not call it from client code or expose it from a public endpoint without your own authorization and input validation.

Sentry

Client Sentry options are stored under public runtime config. Server Sentry options are stored under private runtime config. If sentry.server is omitted, the module reuses matching values from sentry.client.

To suppress expected HTTP errors in both SDKs, use the serializable filter option. For example, this drops every error with an HTTP status below 500:

sentry: {
  client: {
    dsn: process.env.NUXT_PUBLIC_SENTRY_DSN,
    filter: { dropStatusCodesBelow: 500 },
  },
},

beforeSend callbacks cannot be passed through Nuxt runtime config because browser runtime config is serialized. Configure custom callback logic in a project-level sentry.client.config.ts / sentry.server.config.ts instead.

Use the auto-imported sentry.info() helper when you want to send a structured info-level event to Sentry instead of writing a diagnostic console.log:

sentry.info('Checkout completed', {
  tags: {
    feature: 'checkout',
  },
  extra: {
    orderId: 'order_123',
  },
})

The same helper is available in Vue app code and Nitro server code. If you prefer explicit imports inside a Nuxt application, import it from Nuxt's generated imports:

import { sentry } from '#imports'

sentry.info('Background sync finished')

You can also import the standalone helper from the package entry:

import { info } from '@safeki/nuxt'

info('Background sync finished')

The playground includes Sentry buttons for browser and server smoke checks. The server check calls a playground-only /api/sentry-error route that throws a real server exception so Nitro's Sentry error hook can capture it while the UI reports the intentional 500 response as a successful trigger.

Source Maps

When Sentry upload credentials (org / project / authToken) are configured, the module automatically enables client source map generation (sourcemap.client = 'hidden') so the Sentry bundler plugin can upload them. Without this, Sentry logs Source map generation (sourcemap.client) is disabled. If you already set sourcemap.client yourself, the module leaves your value untouched.

Uploading behind a corporate SSL proxy

If a build fails to create a release or upload source maps with an SSL error like:

[60] SSL peer certificate or SSH remote key was not OK
(SSL certificate problem: unable to get local issuer certificate)

your network is intercepting TLS with a proxy whose root certificate sentry-cli (libcurl) does not trust. Two ways to fix it locally:

  1. Provide the corporate CA (recommended, secure). Export your proxy's root certificate to a .pem file and point libcurl at it before building:

    export CURL_CA_BUNDLE=/absolute/path/to/corporate-ca.pem
    npm run build
    
  2. Disable verification (quick, local only). Create a .sentryclirc next to the config being built (e.g. playground/.sentryclirc) — it is git-ignored:

    [http]
    verify_ssl=false
    

    Never commit this or use it in CI/production; it turns off certificate verification entirely.

Module Options

interface ModuleOptions {
  crypto?: {
    secretKey?: string
  }
  slack?: {
    token: string
    channel: string
  }
  sentry?: SentryNuxtModuleOptions & {
    client?: {
      dsn?: string
      environment?: string
      release?: string
      debug?: boolean
      sendDefaultPii?: boolean
      tracesSampleRate?: number
      replaysSessionSampleRate?: number
      replaysOnErrorSampleRate?: number
      tracePropagationTargets?: string[]
      filter?: {
        dropStatusCodesBelow?: number
      }
    }
    server?: {
      dsn?: string
      environment?: string
      release?: string
      debug?: boolean
      sendDefaultPii?: boolean
      tracesSampleRate?: number
      filter?: {
        dropStatusCodesBelow?: number
      }
    }
  }
}

If crypto.secretKey is not provided, the module falls back to process.env.SAFEKIT_CRYPTO_SECRET_KEY.

Playground

Run the playground locally:

npm run dev

The playground includes a browser-only form for testing Safekit-compatible encrypt and decrypt output with Web Crypto. It also includes a runtime config panel backed by a playground-only API route so you can verify decryptedSafekit() decrypts a value inside playground/server/utils/runtime.config.ts while private runtime config is loaded. The playground deliberately does not pass Slack options to the module: with SLACK_BOT_TOKEN and SLACK_CHANNEL configured in playground/.env, GET /api/slack?text=Hello verifies that the Nitro Slack plugin loads credentials at runtime. Add template=request, approved, rejected, or live to exercise the review notification layout; menu, documentTitle, requester, reviewer, and channel can override its sample values. The module itself does not register public crypto or Slack API endpoints.

Development

# Install dependencies
npm install

# Generate stubs and prepare playground
npm run dev:prepare

# Start playground
npm run dev

# Build package
npm run prepack

License

MIT

Version History

Version Released Vue License
0.1.14 ^3.0 MIT
0.1.13 ^3.0 MIT
0.1.12 ^3.0 MIT
0.1.11 ^3.0 MIT
0.1.10 ^3.0 MIT