npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cabin-id/nextjs

v2.2.1

Published

NextJS SDK for CabinID

Readme

CabinID SDK for Next.js

CabinID SDK là bộ công cụ xác thực và quản lý danh tính, cung cấp giải pháp tích hợp dễ dàng cho các ứng dụng Next.js (App Router).

📦 Cài đặt

npm install @cabin-id/nextjs
# hoặc
pnpm add @cabin-id/nextjs
# hoặc
yarn add @cabin-id/nextjs

🚀 Bắt đầu nhanh

1. Thiết lập biến môi trường

Tạo file .env.local trong thư mục gốc dự án:

# CabinID Configuration
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY=cabin_pk_your_publishable_key_here
CABIN_ID_SECRET_KEY=cabin_sk_your_secret_key_here

# URL Redirects (tùy chọn)
NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_UP_URL=/welcome
NEXT_PUBLIC_CABIN_ID_SIGN_IN_URL=/auth
NEXT_PUBLIC_CABIN_ID_SIGN_UP_URL=/auth/register

# API URL (tùy chọn, mặc định: https://api.cabinid.dev/)
NEXT_PUBLIC_CABIN_ID_API_URL=https://api.cabinid.dev/

2. Cấu hình Middleware

Tạo file middleware.ts trong thư mục gốc để bảo vệ các route:

// middleware.ts
import { authMiddleware } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';

const publicRoutes = ['/', '/auth', '/terms', '/api/public/(.*)'];

const isPublicRoute = (path: string) => {
  return publicRoutes.some(route => {
    if (route.includes('(.*)')) {
      const regex = new RegExp('^' + route);
      return regex.test(path);
    }
    return route === path;
  });
};

export default authMiddleware(async (auth, req) => {
  const { userId } = auth();
  const path = req.nextUrl.pathname;

  // 1. Cho phép truy cập các routes công khai
  if (isPublicRoute(path)) {
    return NextResponse.next();
  }

  // 2. Nếu chưa đăng nhập và route cần bảo vệ -> Chuyển hướng đến trang đăng nhập
  if (!userId) {
    const returnUrl = new URL(req.nextUrl.pathname + req.nextUrl.search, req.url);
    return auth().redirectToSignIn({ returnBackUrl: returnUrl.toString() });
  }

  // 3. Đã đăng nhập -> Cho phép truy cập
  return NextResponse.next();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

3. Cấu hình Provider

Bọc ứng dụng với CabinIDProvider trong root layout. Lưu ý: CabinIDProvider là một async Server Component.

// app/layout.tsx
import { CabinIDProvider } from '@cabin-id/nextjs';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="vi">
      <body>
        <CabinIDProvider>
          {children}
        </CabinIDProvider>
      </body>
    </html>
  );
}

� Client ID và Redirect URLs

Khi người dùng được chuyển hướng đến trang đăng nhập/đăng ký của CabinID, SDK tự động thêm client_id vào URL. Điều này giúp CabinID xác định ứng dụng nào đang yêu cầu xác thực.

Cách hoạt động

  1. Publishable Key: Giá trị NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY được sử dụng làm client_id
  2. Redirect URL: URL được xây dựng tự động với format:
    https://{domain}.cabinid.dev/sign-in?client_id={publishable_key}&redirect_url={return_url}

Ví dụ URL được tạo

https://myapp.cabinid.dev/sign-in?client_id=cabin_pk_xxx&redirect_url=https://myapp.com/dashboard

Tham số URL

| Tham số | Mô tả | |---------|-------| | client_id | Publishable key của project, giúp CabinID nhận diện ứng dụng | | redirect_url | URL để quay lại ứng dụng sau khi xác thực thành công |

Lưu ý: client_id được tự động thêm bởi SDK. Bạn không cần xử lý thủ công.

�🔐 Components

SignInButton

Component nút đăng nhập, tự động chuyển hướng đến CabinID.

'use client';

import { SignInButton } from '@cabin-id/nextjs';

export default function LoginPage() {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <SignInButton />
    </div>
  );
}

Props:

| Prop | Type | Default | Mô tả | |------|------|---------|-------| | children | React.ReactNode | "Continue to CabinID" | Nội dung hiển thị | | className | string | Styled button | CSS class tùy chỉnh | | afterSignInUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng nhập | | showLogo | boolean | true | Hiển thị logo CabinID |

Ví dụ tùy chỉnh:

<SignInButton 
  afterSignInUrl="/dashboard"
  showLogo={false}
  className="bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700"
>
  Đăng nhập với CabinID
</SignInButton>

SignOutButton

Component nút đăng xuất với xử lý callback.

'use client';

import { SignOutButton } from '@cabin-id/nextjs';

export default function Header() {
  return (
    <SignOutButton
      onSignOutStart={() => console.log('Đang đăng xuất...')}
      onSignOutSuccess={() => console.log('Đăng xuất thành công!')}
      onSignOutError={(error) => console.error('Lỗi:', error)}
    >
      Đăng xuất
    </SignOutButton>
  );
}

Props:

| Prop | Type | Default | Mô tả | |------|------|---------|-------| | children | React.ReactNode | "Sign Out" | Nội dung hiển thị | | className | string | Styled button | CSS class tùy chỉnh | | onSignOutStart | () => void | - | Callback trước khi đăng xuất | | onSignOutSuccess | () => void | - | Callback khi đăng xuất thành công | | onSignOutError | (error: Error) => void | - | Callback khi có lỗi | | showLoading | boolean | true | Hiển thị trạng thái loading | | loadingText | string | "Signing out..." | Text khi đang loading | | afterSignOutUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng xuất |

🪝 Hooks

useUser

Hook để lấy thông tin người dùng hiện tại trong Client Components.

'use client';

import { useUser } from '@cabin-id/nextjs';

export default function ProfilePage() {
  const { user, isLoaded, isSignedIn, signOut } = useUser();

  if (!isLoaded) {
    return <div>Đang tải...</div>;
  }

  if (!isSignedIn) {
    return <div>Vui lòng đăng nhập để tiếp tục</div>;
  }

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold">Xin chào, {user.firstName}!</h1>
      <p>Email: {user.email}</p>
      <p>ID: {user.id}</p>
      
      <button onClick={() => signOut()}>
        Đăng xuất
      </button>
    </div>
  );
}

Return type:

type UseUserReturn =
  | { isLoaded: false; isSignedIn: undefined; user: undefined; signOut: SignOut }
  | { isLoaded: true; isSignedIn: false; user: null; signOut: SignOut }
  | { isLoaded: true; isSignedIn: true; user: User; signOut: SignOut };

🖥️ Server Functions

auth()

Lấy thông tin xác thực trong Server Components hoặc Server Actions.

// app/dashboard/page.tsx
import { auth } from '@cabin-id/nextjs';

export default async function DashboardPage() {
  const { userId, protect, redirectToSignIn } = await auth();

  // Nếu chưa đăng nhập, redirect đến trang sign-in
  if (!userId) {
    redirectToSignIn();
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <p>User ID: {userId}</p>
    </div>
  );
}

Return type:

type Auth = {
  userId: string | null;
  protect: AuthProtect;
  redirectToSignIn: (opts?: { returnBackUrl?: string }) => never;
};

currentUser()

Lấy thông tin chi tiết người dùng từ server.

// app/profile/page.tsx
import { currentUser } from '@cabin-id/nextjs';

export default async function ProfilePage() {
  const user = await currentUser();

  if (!user) {
    return <div>Vui lòng đăng nhập</div>;
  }

  return (
    <div>
      <h1>Hồ sơ cá nhân</h1>
      <p>Tên: {user.firstName} {user.lastName}</p>
      <p>Email: {user.email}</p>
    </div>
  );
}

🛡️ Bảo vệ API Routes

Sử dụng auth()

// app/api/profile/route.ts
import { auth } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';

export async function GET() {
  const { userId } = await auth();
  
  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Lấy dữ liệu người dùng từ database
  const userData = await getUserFromDatabase(userId);

  return NextResponse.json({ user: userData });
}

🎨 Tùy chỉnh giao diện

AuthButton Component

AuthButton là component tiện ích đã được tích hợp sẵn trong SDK, tự động chuyển đổi giữa SignInButtonSignOutButton dựa trên trạng thái đăng nhập.

'use client';

import { AuthButton } from '@cabin-id/nextjs';

export default function Header() {
  return (
    <nav className="flex justify-between items-center p-4">
      <h1>My App</h1>
      <AuthButton 
        signInLabel="Đăng nhập"
        signOutLabel="Đăng xuất"
        afterSignInUrl="/dashboard"
        afterSignOutUrl="/"
      />
    </nav>
  );
}

Props:

| Prop | Type | Default | Mô tả | |------|------|---------|-------| | signInLabel | React.ReactNode | "Sign In" | Text hiển thị trên nút đăng nhập | | signOutLabel | React.ReactNode | "Sign Out" | Text hiển thị trên nút đăng xuất | | className | string | - | CSS class tùy chỉnh | | afterSignInUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng nhập | | afterSignOutUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng xuất | | showLogo | boolean | true | Hiển thị logo CabinID (chỉ áp dụng cho nút đăng nhập) |

Ví dụ nâng cao:

<AuthButton 
  signInLabel={
    <span className="flex items-center gap-2">
      <UserIcon className="w-4 h-4" />
      Đăng nhập
    </span>
  }
  signOutLabel="Thoát"
  className="bg-blue-600 text-white px-4 py-2 rounded-lg"
/>

Protected Route Component

Component bảo vệ các trang yêu cầu đăng nhập:

'use client';

import { useUser } from '@cabin-id/nextjs';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

interface ProtectedRouteProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

export default function ProtectedRoute({ 
  children, 
  fallback = <div>Đang chuyển hướng...</div> 
}: ProtectedRouteProps) {
  const { isSignedIn, isLoaded } = useUser();
  const router = useRouter();

  useEffect(() => {
    if (isLoaded && !isSignedIn) {
      router.push('/auth');
    }
  }, [isLoaded, isSignedIn, router]);

  if (!isLoaded) {
    return <div>Đang tải...</div>;
  }

  if (!isSignedIn) {
    return fallback;
  }

  return <>{children}</>;
}

📚 API Reference

Exports từ @cabin-id/nextjs

| Export | Type | Mô tả | |--------|------|-------| | CabinIDProvider | Component (async) | Provider chính, bọc ứng dụng | | SignInButton | Component | Nút đăng nhập | | SignOutButton | Component | Nút đăng xuất | | AuthButton | Component | Nút đăng nhập/đăng xuất tự động | | useUser | Hook | Lấy thông tin người dùng (client) | | auth | Function | Lấy thông tin xác thực (server) | | currentUser | Function | Lấy chi tiết người dùng (server) | | authMiddleware | Function | Middleware xác thực |

Exports từ @cabin-id/nextjs/server

| Export | Type | Mô tả | |--------|------|-------| | authMiddleware | Function | Middleware xác thực cho Next.js |

Subdomain Utilities

import { 
  detectCabinIdSubdomain, 
  storeCabinIdSubdomain, 
  clearCabinIdSubdomain 
} from '@cabin-id/nextjs';

🔧 Cấu hình nâng cao

Middleware với Custom Logic

// middleware.ts
import { authMiddleware } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';

const adminRoutes = ['/admin', '/admin/(.*)'];
const publicRoutes = ['/', '/about', '/contact'];

export default authMiddleware(async (auth, req) => {
  const { userId } = auth();
  const path = req.nextUrl.pathname;

  // Public routes
  if (publicRoutes.some(r => path === r || path.match(new RegExp(`^${r}$`)))) {
    return NextResponse.next();
  }

  // Require authentication
  if (!userId) {
    return auth().redirectToSignIn({ returnBackUrl: req.url });
  }

  // Admin routes - thêm logic kiểm tra quyền admin
  if (adminRoutes.some(r => path.match(new RegExp(`^${r}$`)))) {
    // TODO: Kiểm tra quyền admin từ database
    // const isAdmin = await checkAdminRole(userId);
    // if (!isAdmin) {
    //   return NextResponse.redirect(new URL('/unauthorized', req.url));
    // }
  }

  return NextResponse.next();
});

🔔 Webhook Integration

CabinID hỗ trợ webhook để thông báo cho ứng dụng của bạn khi có các sự kiện xác thực xảy ra.

Cấu hình Webhook

  1. Đăng nhập vào CabinID Dashboard
  2. Chọn project của bạn
  3. Vào SettingsWebhooks
  4. Thêm URL endpoint của bạn (ví dụ: https://yourapp.com/api/webhooks/cabinid)

Các loại sự kiện

| Event Type | Mô tả | |------------|-------| | sign-in | Người dùng đăng nhập thành công | | sign-up | Người dùng đăng ký tài khoản mới | | sign-out | Người dùng đăng xuất |

Webhook Payload

Khi một sự kiện xảy ra, CabinID sẽ gửi POST request đến webhook URL của bạn với payload sau:

interface WebhookPayload {
  eventType: 'sign-in' | 'sign-up' | 'sign-out';
  date: number; // Unix timestamp (milliseconds)
  payload: {
    user: {
      id: string;
      avatar: string;
      email: string;
      phoneNumber: string;
      firstName: string;
      lastName: string;
      createdAt: number;
      updatedAt: number;
    };
  };
}

Xử lý Webhook trong Next.js

// app/api/webhooks/cabinid/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface CabinIDWebhookPayload {
  eventType: 'sign-in' | 'sign-up' | 'sign-out';
  date: number;
  payload: {
    user: {
      id: string;
      email: string;
      firstName: string;
      lastName: string;
    };
  };
}

export async function POST(request: NextRequest) {
  try {
    const body: CabinIDWebhookPayload = await request.json();
    
    const { eventType, payload } = body;
    const { user } = payload;

    switch (eventType) {
      case 'sign-up':
        // Tạo user mới trong database của bạn
        await createUserInDatabase(user);
        console.log(`New user registered: ${user.email}`);
        break;
        
      case 'sign-in':
        // Cập nhật last login
        await updateLastLogin(user.id);
        console.log(`User signed in: ${user.email}`);
        break;
        
      case 'sign-out':
        // Xử lý đăng xuất (nếu cần)
        console.log(`User signed out: ${user.email}`);
        break;
    }

    return NextResponse.json({ received: true }, { status: 200 });
  } catch (error) {
    console.error('Webhook error:', error);
    return NextResponse.json(
      { error: 'Webhook processing failed' },
      { status: 500 }
    );
  }
}

Best Practices cho Webhook

  1. Respond nhanh: Trả về response 200 ngay lập tức, sau đó xử lý async
  2. Idempotency: Xử lý trường hợp webhook được gửi nhiều lần
  3. Logging: Log tất cả webhook events để debug
  4. Error handling: Xử lý lỗi gracefully, không để crash server
// Ví dụ với background processing
export async function POST(request: NextRequest) {
  const body = await request.json();
  
  // Respond ngay lập tức
  // Xử lý trong background (ví dụ: queue job)
  processWebhookAsync(body).catch(console.error);
  
  return NextResponse.json({ received: true });
}

🐛 Xử lý lỗi thường gặp

Lỗi: "useUser must be used within CabinIDProvider"

Đảm bảo bạn đã bọc ứng dụng với CabinIDProvider trong root layout:

// ✅ Đúng
<CabinIDProvider>
  <YourApp />
</CabinIDProvider>

// ❌ Sai - thiếu provider
<YourApp />

Lỗi: Environment variables không được tìm thấy

Kiểm tra file .env.local:

# ✅ Đúng - có prefix NEXT_PUBLIC_ cho client-side
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY=cabin_pk_...
CABIN_ID_SECRET_KEY=cabin_sk_...

# ❌ Sai - thiếu NEXT_PUBLIC_ prefix
CABIN_ID_PUBLISH_KEY=cabin_pk_...

Lỗi: Middleware không hoạt động

Đảm bảo file middleware.ts nằm ở thư mục gốc (cùng cấp với app/ hoặc src/):

project-root/
├── app/
├── middleware.ts  ✅
└── .env.local

🔗 Liên kết hữu ích

📄 Giấy phép

MIT License - xem file LICENSE để biết thêm chi tiết.


🚀 Bắt đầu ngay hôm nay! Tích hợp CabinID vào ứng dụng của bạn chỉ trong vài phút.