clerk javascript sdk
Set up user authentication in vanilla JavaScript apps with Clerk
$ npx docs2skills add clerk-javascript-authClerk JavaScript SDK
Set up user authentication in vanilla JavaScript apps with Clerk
What this skill does
Clerk's JavaScript SDK provides a complete authentication system for vanilla JavaScript applications through prebuilt UI components and session management. It handles sign-up, sign-in, password reset, multi-factor authentication, and user profile management without requiring you to build custom forms or manage authentication state manually.
The SDK integrates with any JavaScript bundler (Vite, Webpack, Rollup) and provides methods to mount authentication components directly into your DOM. It automatically manages JWT tokens, session persistence, and user state across page refreshes. Clerk handles the security complexities of modern authentication including CSRF protection, secure cookie handling, and OAuth integrations.
Unlike framework-specific solutions, this vanilla SDK works with any JavaScript architecture - whether you're building a single-page application, adding auth to an existing site, or working without a frontend framework. The prebuilt components are fully customizable and handle edge cases like loading states, error handling, and accessibility.
Prerequisites
- Node.js 14+ for development build tools
- A bundler like Vite, Webpack, or Rollup (ES modules required)
- Clerk account and publishable API key from dashboard.clerk.com
- Modern browser with ES6+ support for end users
Quick start
npm create vite@latest my-auth-app
cd my-auth-app
npm install @clerk/clerk-js
Create .env file:
VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here
Update src/main.js:
import { Clerk } from '@clerk/clerk-js'
const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(publishableKey)
await clerk.load()
if (clerk.isSignedIn) {
document.getElementById('app').innerHTML = `<div id="user-button"></div>`
clerk.mountUserButton(document.getElementById('user-button'))
} else {
document.getElementById('app').innerHTML = `<div id="sign-in"></div>`
clerk.mountSignIn(document.getElementById('sign-in'))
}
Core concepts
Clerk Instance: The main class that manages authentication state and provides methods to interact with Clerk's services. You create one instance per application and call load() to initialize it asynchronously.
Prebuilt Components: Ready-made UI components like SignIn, SignUp, UserButton that handle all authentication flows. These mount to DOM elements and include styling, form validation, and error handling out of the box.
Session Management: Clerk automatically manages user sessions, storing encrypted tokens in secure cookies. The isSignedIn property and user object provide reactive access to authentication state.
Component Mounting: Instead of importing React components, you mount Clerk components to existing DOM elements using methods like mountSignIn() and mountUserButton(). This allows integration with any JavaScript architecture.
Key API surface
| Method | Description |
|---|---|
new Clerk(publishableKey) | Creates Clerk instance with your API key |
clerk.load(options?) | Initializes Clerk, loads user session |
clerk.isSignedIn | Boolean indicating if user is authenticated |
clerk.user | Current user object with profile data |
clerk.mountSignIn(element, props?) | Renders sign-in form in DOM element |
clerk.mountSignUp(element, props?) | Renders sign-up form in DOM element |
clerk.mountUserButton(element, props?) | Renders user menu button |
clerk.mountUserProfile(element, props?) | Renders user profile management |
clerk.signOut() | Signs out current user |
clerk.addListener(event, callback) | Listens for auth state changes |
clerk.navigate(to) | Programmatic navigation after auth |
clerk.redirectToSignIn() | Redirect to hosted sign-in page |
Common patterns
Protected routes with redirect:
if (!clerk.isSignedIn) {
clerk.redirectToSignIn()
return
}
// Render protected content
Listen for authentication changes:
clerk.addListener('user', (user) => {
if (user) {
// User signed in, update UI
renderDashboard()
} else {
// User signed out, show login
renderSignIn()
}
})
Custom sign-up with redirect:
clerk.mountSignUp(signUpElement, {
redirectUrl: '/dashboard',
signInUrl: '/sign-in'
})
Conditional component mounting:
await clerk.load()
const targetDiv = document.getElementById('auth')
if (clerk.isSignedIn) {
clerk.mountUserButton(targetDiv)
} else {
clerk.mountSignIn(targetDiv)
}
Configuration
Load options (passed to clerk.load()):
routing: 'path' | 'hash'- URL routing strategy (default: 'path')navigate: (to: string) => void- Custom navigation functionallowedRedirectOrigins: string[]- Allowed redirect URLs for security
Environment variables:
VITE_CLERK_PUBLISHABLE_KEY- Your Clerk publishable key (Vite)CLERK_PUBLISHABLE_KEY- Generic environment variable name
Component props (passed to mount methods):
redirectUrl: string- Where to redirect after auth successsignUpUrl: string- Link between sign-in and sign-up pagessignInUrl: string- Link between sign-up and sign-in pagesappearance: object- Custom styling and theming options
Best practices
Always call clerk.load() before checking isSignedIn or accessing user to ensure session is initialized.
Use environment variables for publishable keys, never hardcode them in client-side code.
Implement loading states while clerk.load() completes - the promise can take time on slow networks.
Add error boundaries around component mounting since network issues can cause mount methods to throw.
Use clerk.addListener() to react to auth state changes rather than polling isSignedIn.
Keep one Clerk instance per application - don't create multiple instances as they won't share state.
For SPA routing, pass a custom navigate function to load() to integrate with your router.
Gotchas and common mistakes
- Must await
clerk.load(): CheckingisSignedInbefore load completes returns stale data - Publishable key format: Must start with
pk_test_orpk_live_, verify you're using publishable not secret key - Environment variable naming: Vite requires
VITE_prefix, other bundlers may differ - Mount element timing: Ensure DOM elements exist before calling mount methods or they'll throw
- Multiple mounts: Mounting to the same element twice without unmounting causes duplicate components
- Session persistence: Signing out in one tab doesn't immediately update other tabs - use listeners
- HTTPS requirement: Clerk requires HTTPS in production, localhost works for development
- Bundle size: Import only from
@clerk/clerk-js, not framework-specific packages - Component lifecycle: Mounted components handle their own cleanup, but manual event listeners need cleanup
- Redirect loops: Setting
redirectUrlto current page can cause infinite redirects - Cross-origin issues: Ensure your domain is added to Clerk dashboard's allowed origins
- Server-side rendering: This SDK is client-only, use
@clerk/nextjsor similar for SSR frameworks