Loomup Docs
Guide /docs/sdk-nuxt

Nuxt SDK

Package: @loomup/nuxt (SDK repository path: packages/nuxt).

Nuxt 3/4 module on top of @loomup/client: cookie-backed sessions, Nitro server helpers, auth API routes, and Vue composables.

Session model

Loomup Nuxt uses app-owned HttpOnly cookies on your Nuxt origin:

CookiePurpose
loomup-accessJWT access token (same name as @loomup/next / @loomup/astro)
loomup-refreshRefresh token

Requests to Loomup use the Authorization: Bearer header. Middleware and server clients read cookies, then attach the access token.

This is not the same as Loomup server auth.cookie_mode (which sets loomup_access / loomup_refresh with underscores on the Loomup origin). Prefer the Nuxt cookie approach for cross-origin setups (e.g. Nuxt on :3001, Loomup on :3000).

Install

bash
cd packages/client && npm install && npm run build
cd ../nuxt && npm install && npm run build

# in your Nuxt app
npm install @loomup/client @loomup/nuxt
# or file: paths during monorepo development

Peer: Nuxt ≥ 3, Vue ≥ 3.

For SPA-only Vue (no Nuxt), use @loomup/vue composables with @loomup/client directly. This package adds Nuxt module wiring, cookie sessions, and auth routes.

Module setup

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@loomup/nuxt"],
  loomup: {
    url: process.env.LOOMUP_URL ?? "http://127.0.0.1:3000",
    // authRoutes: true,          // default — /api/auth/*
    // sessionMiddleware: true,   // default — proactive access refresh
    // authBasePath: "/api/auth",
    // exposeAccessToken: true,   // session/login JSON includes access_token
    // skewSeconds: 60,
  },
  runtimeConfig: {
    loomupUrl: process.env.LOOMUP_URL ?? "http://127.0.0.1:3000",
    public: {
      loomupUrl:
        process.env.NUXT_PUBLIC_LOOMUP_URL ?? "http://127.0.0.1:3000",
    },
  },
});

Module options

OptionDefaultRole
urlenv / emptyLoomup base URL
authRoutestrueRegister login/register/logout/refresh/session handlers
authBasePath/api/authPrefix for auth routes
sessionMiddlewaretrueNitro middleware that refreshes near-expired access tokens
skewSeconds60Refresh when access JWT expires within this many seconds
exposeAccessTokentrueInclude access_token in auth JSON for client hydrate
cookiesdefaultsCookie name / maxAge / secure overrides

Auth routes

When authRoutes is enabled:

MethodPathBehavior
POST/api/auth/loginsignIn → Set-Cookie + optional access_token
POST/api/auth/registersignUp → same
POST/api/auth/logoutsignOut + clear cookies
POST/api/auth/refreshrefresh + rotate cookies
GET/api/auth/sessionme + hydrate payload (user, optional access_token)

Prefer calling these from the browser so refresh tokens never enter localStorage.

Server

ts
// server/api/todos.get.ts
import { createServerClient, resolveLoomupUrl } from "@loomup/nuxt/server";
import { getCookie, setCookie } from "h3";

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event);
  const client = createServerClient({
    url: resolveLoomupUrl({
      loomupUrl: config.loomupUrl as string,
      public: config.public as { loomupUrl?: string },
    }),
    event,
    cookieAdapter: { getCookie, setCookie },
  });

  // REST only — do not call subscribe() during SSR
  const { data } = await client.from("todos").select({ limit: 20, sort: "-id" });
  return data;
});

Object storage (server)

ts
// server/api/upload.post.ts
import {
  createServerClient,
  resolveLoomupUrl,
  uploadFromFormData,
  storageDownloadResponse,
} from "@loomup/nuxt";
// or from "@loomup/nuxt/server" for createServerClient + helpers

export default defineEventHandler(async (event) => {
  const client = createServerClient({ /* url + event + cookieAdapter */ });
  const form = await readFormData(event);
  const meta = await uploadFromFormData(client, "avatars", form, {
    pathPrefix: "uploads/",
    upsert: true,
  });
  return { data: meta };
});

Direct API: await client.storage.from("avatars").upload(...). See storage.md.

You can also pass a framework-agnostic cookie jar:

ts
createServerClient({
  url,
  cookies: {
    getAll: () => [...],
    setAll: (records) => { /* set cookies */ },
  },
});

Token rotation during a request (401 → refresh) invokes onTokens, which writes cookies via the adapter. Proactive refresh runs in session middleware.

Realtime on the server

Do not open WebSocket subscriptions during a request-scoped SSR render. Use REST on the server; use client composables for realtime.

Client composables

Auto-imported when the module is enabled:

ComposableRole
useLoomup()Shared browser LoomupClient
useLoomupState()Client + user + setSession / refreshSession
useAuth()signIn / signUp / signOut via Nuxt auth routes; hydrates access token
useLiveQuery(table, opts?)Initial select + subscribe (refetch | merge)
vue
<script setup lang="ts">
const { user, signIn, signOut, loading, error } = useAuth();
const { data: todos, ready } = useLiveQuery("todos", {
  limit: 50,
  strategy: "merge",
});

async function add(title: string) {
  const client = useLoomup();
  await client.from("todos").insert({ title, completed: 0 });
}
</script>

The client plugin hydrates from GET /api/auth/session on load so realtime can use an in-memory access token while the refresh token stays HttpOnly.

Package exports

ImportPurpose
@loomup/nuxtNuxt module (modules: ['@loomup/nuxt'])
@loomup/nuxt/servercreateServerClient, resolveLoomupUrl, cookie helpers

Security

  • Keep refresh tokens HttpOnly (default cookie helpers do this).
  • Use Secure in production (NODE_ENV=production sets Secure by default).
  • SameSite=Lax is the default.
  • Prefer auth through your Nuxt origin rather than storing refresh tokens in localStorage.

Develop / test this package

bash
cd packages/nuxt
npm install
npm test   # tsc + node:test

Example

See examples/nuxt-todo.

See also