@lenne.tech/nuxt-extensions
v1.5.4
Published
Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects
Downloads
1,123
Readme
@lenne.tech/nuxt-extensions
Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects.
Quick Start
Want to start a new fullstack project?
Use the nuxt-base-starter (Frontend) together with the nest-server-starter (Backend) to initialize a complete fullstack project with this package pre-configured.
Both starters are designed to work seamlessly together and serve as reference implementations showing how to use all features in a real application.
Quick initialization:
npx @lenne.tech/cli fullstack init my-project
Installation
npm install @lenne.tech/nuxt-extensions better-auth
# Optional: For passkey support
npm install @better-auth/passkey
# Optional: For TUS file uploads
npm install tus-js-clientFeatures
- Better-Auth Integration - Login, 2FA, Passkey/WebAuthn support
- Cookie/JWT Dual-Mode - Automatic fallback from cookies to JWT
- TUS File Upload - Resumable uploads with pause/resume
- Transition Components - Ready-to-use Vue transition wrappers
- i18n Support - English and German translations (works without i18n too)
- Auto-imports - All composables and components are auto-imported
Environment Variables
| Variable | Context | Description |
|----------|---------|-------------|
| NUXT_PUBLIC_API_URL | Client + Server | Public API URL. Primary way to configure the API endpoint. Used for client-side requests and as SSR fallback. |
| NUXT_API_URL | Server only | Internal API URL for SSR requests. Use when the backend has a private network address that should not be exposed to the client. |
| NUXT_PUBLIC_API_PROXY | Client | Set to true to enable the Vite dev proxy. Routes client requests through /api/ for same-origin cookies. Only for local development. |
How URL Resolution Works
All environment variables are resolved at runtime (not build time). This means you can build a Docker image once and deploy it to different environments by changing env vars — no rebuild needed.
SSR fallback chain:
NUXT_API_URL → NUXT_PUBLIC_API_URL → auth.baseURL (from nuxt.config.ts) → http://localhost:3000
Client fallback chain (no proxy):
NUXT_PUBLIC_API_URL → auth.baseURL (from nuxt.config.ts) → http://localhost:3000
Client with proxy (NUXT_PUBLIC_API_PROXY=true):
All requests go to /api/{path} — the Vite dev proxy forwards them to the backend.
Security:
NUXT_API_URLis never exposed to the client bundle. It stays inruntimeConfig.apiUrl(server only). This is important when using internal network addresses likehttp://api.svc.cluster.local.
Deployment Scenarios
Local development — Frontend and backend on different ports, proxy ensures same-origin cookies:
NUXT_PUBLIC_API_URL=http://localhost:3000
NUXT_PUBLIC_API_PROXY=trueProduction (simple) — Backend reachable via public URL from both SSR and client:
NUXT_PUBLIC_API_URL=https://api.example.comProduction (internal network) — SSR uses fast internal route, client uses public URL:
NUXT_PUBLIC_API_URL=https://api.example.com
NUXT_API_URL=http://api-internal:3000Legacy (nuxt.config.ts only) — Works but env vars are preferred for runtime flexibility:
// nuxt.config.ts
ltExtensions: {
auth: {
baseURL: 'https://api.example.com',
},
}Configuration
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@lenne.tech/nuxt-extensions'],
ltExtensions: {
// Auth configuration
auth: {
enabled: true, // Enable auth features
baseURL: '', // API base URL (empty = use env vars)
basePath: '/iam', // Better-Auth endpoint prefix
loginPath: '/auth/login', // Login redirect path
twoFactorRedirectPath: '/auth/2fa', // 2FA redirect path
// Plugin options
enableAdmin: true, // Admin plugin
enableTwoFactor: true, // 2FA plugin
enablePasskey: true, // Passkey/WebAuthn plugin
// Interceptor options
interceptor: {
enabled: true, // 401 auto-handler
publicPaths: ['/auth/login', '/auth/register'],
},
// System setup (first admin user creation)
systemSetup: {
enabled: false, // Enable setup flow
setupPath: '/auth/setup', // Setup page path
},
},
// Error translation configuration
errorTranslation: {
enabled: true, // Translate backend error codes
defaultLocale: 'de', // Fallback locale
},
// TUS upload configuration
tus: {
defaultEndpoint: '/files/upload',
defaultChunkSize: 5 * 1024 * 1024, // 5MB
},
// i18n configuration (optional)
i18n: {
autoMerge: true, // Auto-merge locales with @nuxtjs/i18n
},
},
});Usage
Authentication
<script setup>
// Auth composable (auto-imported)
const {
user,
isAuthenticated,
isLoading,
signIn,
signOut,
authenticateWithPasskey,
registerPasskey,
twoFactor,
} = useLtAuth();
// Login with email/password
async function handleLogin(email: string, password: string) {
const result = await signIn.email({ email, password });
if (result.requiresTwoFactor) {
navigateTo('/auth/2fa');
}
}
// Passkey login
async function handlePasskeyLogin() {
const result = await authenticateWithPasskey();
if (result.success) {
navigateTo('/dashboard');
}
}
</script>
<template>
<div v-if="isAuthenticated">
Welcome, {{ user?.name }}!
<button @click="signOut()">Logout</button>
</div>
</template>Custom Better Auth Plugins
You can extend the auth client with additional Better Auth plugins:
Option 1: Plugin Registration (recommended)
Create a Nuxt plugin to register plugins before the auth client is initialized:
// plugins/auth-plugins.client.ts
import { registerLtAuthPlugins } from '@lenne.tech/nuxt-extensions/lib';
import { organizationClient, magicLinkClient } from 'better-auth/client/plugins';
export default defineNuxtPlugin(() => {
registerLtAuthPlugins([
organizationClient(),
magicLinkClient(),
]);
});Note: In Vue components,
registerLtAuthPluginsis auto-imported. In.tsfiles (like Nuxt plugins), import from@lenne.tech/nuxt-extensions/lib.
Option 2: Direct Factory Usage
For full control, create the auth client directly with your plugins:
import { createLtAuthClient } from '@lenne.tech/nuxt-extensions/lib';
import { organizationClient } from 'better-auth/client/plugins';
const authClient = createLtAuthClient({
plugins: [organizationClient()],
});
// Use authClient.organization.* methodsAvailable Better Auth Plugins:
organizationClient- Organization/team managementmagicLinkClient- Passwordless email loginoneTapClient- Google One Tap loginanonymousClient- Anonymous/guest sessions- See Better Auth Plugins for full list
TUS File Upload
<script setup>
const {
addFiles,
uploads,
totalProgress,
isUploading,
pauseUpload,
resumeUpload,
cancelUpload,
} = useLtTusUpload({
endpoint: '/api/files/upload',
onSuccess: (item) => console.log('Uploaded:', item.url),
onError: (item, error) => console.error('Failed:', error),
});
const { formatFileSize } = useLtFile();
function handleFileSelect(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files) {
addFiles(Array.from(input.files));
}
}
</script>
<template>
<div>
<input type="file" multiple @change="handleFileSelect" />
<div v-for="upload in uploads" :key="upload.id">
<span>{{ upload.file.name }}</span>
<span>{{ formatFileSize(upload.progress.bytesUploaded) }} / {{ formatFileSize(upload.progress.bytesTotal) }}</span>
<span>{{ upload.progress.percentage }}%</span>
<button v-if="upload.status === 'uploading'" @click="pauseUpload(upload.id)">
Pause
</button>
<button v-if="upload.status === 'paused'" @click="resumeUpload(upload.id)">
Resume
</button>
</div>
<div v-if="isUploading">
Total Progress: {{ totalProgress.percentage }}%
</div>
</div>
</template>Transition Components
<template>
<!-- Fade transition -->
<LtTransitionFade>
<div v-if="show">Content with fade</div>
</LtTransitionFade>
<!-- Fade with scale -->
<LtTransitionFadeScale :start-duration="200" :leave-duration="150">
<div v-if="show">Content with fade and scale</div>
</LtTransitionFadeScale>
<!-- Slide from right -->
<LtTransitionSlide>
<div v-if="show">Content slides from right</div>
</LtTransitionSlide>
<!-- Slide from bottom -->
<LtTransitionSlideBottom>
<div v-if="show">Content slides from bottom</div>
</LtTransitionSlideBottom>
<!-- Slide from left -->
<LtTransitionSlideRevert>
<div v-if="show">Content slides from left</div>
</LtTransitionSlideRevert>
</template>Web Share API
<script setup>
const { share } = useLtShare();
async function handleShare() {
await share('Check this out!', 'Amazing content');
// Uses native share on mobile, clipboard fallback on desktop
}
</script>Utilities
// Tailwind TypeScript helper
const buttonClasses = tw`bg-blue-500 hover:bg-blue-700 text-white`;
// Crypto utilities (for WebAuthn)
const hash = await ltSha256('password');
const base64 = ltArrayBufferToBase64Url(buffer);
const uint8 = ltBase64UrlToUint8Array(base64String);i18n Support
The package works with or without @nuxtjs/i18n:
| Setup | Language | Text Source |
|-------|----------|-------------|
| Without i18n | German | Hardcoded fallback texts |
| With i18n, Locale: de | German | From de.json |
| With i18n, Locale: en | English | From en.json |
| With i18n, other Locale | English | Fallback to en.json |
API Reference
Composables
| Composable | Description |
|------------|-------------|
| useLtAuth() | Better-Auth integration with session, passkey, 2FA |
| useLtAuthClient() | Direct access to the Better-Auth client singleton |
| useLtErrorTranslation() | Translate backend error codes to user-friendly messages |
| useLtTusUpload() | TUS protocol file uploads with pause/resume |
| useLtFile() | File utilities (size formatting, URLs) |
| useLtShare() | Web Share API with clipboard fallback |
| useSystemSetup() | System setup flow for initial admin user creation |
Components
| Component | Description |
|-----------|-------------|
| <LtTransitionFade> | Opacity fade transition |
| <LtTransitionFadeScale> | Fade with scale transition |
| <LtTransitionSlide> | Slide from right transition |
| <LtTransitionSlideBottom> | Slide from bottom transition |
| <LtTransitionSlideRevert> | Slide from left transition |
Utilities
| Utility | Description |
|---------|-------------|
| tw | Tailwind TypeScript helper |
| ltSha256() | SHA256 hash function |
| ltArrayBufferToBase64Url() | ArrayBuffer to base64url conversion |
| ltBase64UrlToUint8Array() | Base64url to Uint8Array conversion |
| createLtAuthClient() | Auth client factory for custom configuration |
| registerLtAuthPlugins() | Register custom Better Auth plugins |
Related Projects
| Project | Description | |---------|-------------| | nuxt-base-starter | Frontend starter template (uses this package) | | nest-server-starter | Backend starter template (Better-Auth backend) | | @lenne.tech/nest-server | Backend framework with Better-Auth support | | @lenne.tech/cli | CLI tool for fullstack project initialization |
Fullstack Architecture
+-------------------------------------------------------------+
| Your Fullstack App |
+-----------------------------+-------------------------------+
| Frontend | Backend |
| (nuxt-base-starter) | (nest-server-starter) |
+-----------------------------+-------------------------------+
| @lenne.tech/nuxt-extensions| @lenne.tech/nest-server |
| - useLtAuth() | - CoreBetterAuthModule |
| - useLtTusUpload() | - CoreFileModule |
| - <LtTransition*> | - CoreUserModule |
+-----------------------------+-------------------------------+Development
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with playground
npm run dev
# Build the module
npm run build
# Run tests
npm run test