Same underlying idea as the React guide — construct the client once, do mount-time work in the right lifecycle hook, trigger auth calls from real user actions — expressed with Vue's Composition API primitives instead of React's.
Put new Threshold1(...) in its own module, not inside a component's <script setup>:
// src/auth.ts
import { Threshold1 } from "@threshold1/auth";
export const auth = new Threshold1({
apiKey: "th_test_...",
auth: { fallback: "smart" },
});
This matters more in Vue than it might look. <script setup> compiles down to a component's setup() function, and Vue only re-runs setup() when a component is freshly instantiated — not on every reactive update, so it's not the same "runs on every render" trap React's function-component body has. But it does re-run on every mount, including a remount: if this component sits behind v-if, or is a routed page a user navigates away from and back to, constructing auth directly inside <script setup> would build a brand-new instance — and silently drop any in-memory session — each time. A separate module, imported wherever it's needed, is a true singleton for the app's entire lifetime regardless of what any individual component does. This is also just the standard Vue convention for a shared client (the same shape you'd use for an API client or a shared service), not something specific to this SDK.
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { auth } from "./auth";
const email = ref("");
// Runs once when this component mounts — the right place for anything
// that should happen automatically on load, like picking up a
// magic-link redirect.
onMounted(() => {
auth.resumeSession().then((user) => {
if (user) {
// already logged in via a magic-link click
}
});
});
// Triggered by a real user action — the right place for register()/login().
async function handleSubmit() {
const result = await auth.login(email.value);
if (result.status === "success") {
// logged in
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="email" />
<button type="submit">Log in</button>
</form>
</template>
This exact pattern — live-verified end to end in a real Vite+Vue app against the real hosted API: real sendOtp()/verifyOtp() round trip, real email, real code, real session, zero console warnings or errors.
Checked rather than assumed: Vue has no equivalent to React's StrictMode double-invoking effects in development. onMounted fired exactly once on a real page load in this verification, and resumeSession() resolved exactly once — no double-firing, no state surprises to design around.
There's no @threshold1/vue, and there won't be — the SDK is deliberately framework-agnostic (see Core Concepts), and framework guides like this one exist to show the correct usage pattern for the one real package, not to hand out a thin wrapper around it. Everything above is example code you own and can adapt, not an API surface threshold1 maintains.