Agent skill

Supabase Nextjs

by alinaqi in alinaqi/maggy

“Next.js with Supabase and Drizzle ORM”

— description from SKILL.md by alinaqi
MITAuto-check: notesDatabases

Install Supabase Nextjs

skills CLI
$ npx skills add alinaqi/maggy --skill supabase-nextjs -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install alinaqi/maggy supabase-nextjs --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/supabase-nextjs .claude/skills/supabase-nextjs && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
supabase-nextjs
GitHub stars
707
Token cost
~4.3k tokens
SKILL.md length
172 words
Files
1
Skills in repo
71
Repo updated
First seen
Licence
MIT

At a glance

  • SKILL.md covers Core Principle, Project Structure, Setup and Drizzle Setup, plus 7 more sections
  • Calls npm and supabase; needs NEXT_PUBLIC_SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY

About this skill

Supabase Nextjs is a skill in alinaqi/maggy (707 stars). Its SKILL.md is about 4.3k tokens. Licence: MIT.

What it can do on your machine

Read from SKILL.md and the folder at commit 72a456e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npm
    • supabase

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • supabase.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • NEXT_PUBLIC_SUPABASE_ANON_KEY
    • SUPABASE_SERVICE_ROLE_KEY

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Supabase Nextjs loads about 4.3k tokens when it runs. Until then it costs about 13 tokens; SKILL.md has 172 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~13
When it runs · the whole SKILL.md, loaded when a task matches
~4.3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteMentions a .env fileSKILL.md:61
    └── .env.local
  • NoteMentions a .env fileSKILL.md:76
    # .env.local

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from alinaqi/maggy at commit 72a456e, republished under its MIT licence (© alinaqi). 172 words, ~4,290 tokens.

Download SKILL.mdSave it as .claude/skills/supabase-nextjs/SKILL.md (or your agent's skills folder).
name
supabase-nextjs
description
Next.js with Supabase and Drizzle ORM
when-to-use
When building a Next.js app with Supabase backend
user-invocable
false
paths
src/app/**, src/db/**, supabase/**
effort
medium

Supabase + Next.js Skill

Next.js App Router patterns with Supabase Auth and Drizzle ORM.

Sources: Supabase Next.js Guide | Drizzle + Supabase


Core Principle

Drizzle for queries, Supabase for auth/storage, server components by default.

Use Drizzle ORM for type-safe database access. Use Supabase client for auth, storage, and realtime. Prefer server components; use client components only when needed.


Project Structure

project/
├── src/
│   ├── app/
│   │   ├── (auth)/
│   │   │   ├── login/page.tsx
│   │   │   ├── signup/page.tsx
│   │   │   └── callback/route.ts
│   │   ├── (dashboard)/
│   │   │   └── page.tsx
│   │   ├── api/
│   │   │   └── [...]/route.ts
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components/
│   │   ├── auth/
│   │   └── ui/
│   ├── db/
│   │   ├── index.ts              # Drizzle client
│   │   ├── schema.ts             # Schema definitions
│   │   └── queries/              # Query functions
│   ├── lib/
│   │   ├── supabase/
│   │   │   ├── client.ts         # Browser client
│   │   │   ├── server.ts         # Server client
│   │   │   └── middleware.ts     # Auth middleware helper
│   │   └── auth.ts               # Auth helpers
│   └── middleware.ts             # Next.js middleware
├── supabase/
│   ├── migrations/
│   └── config.toml
├── drizzle.config.ts
└── .env.local

Setup

Install Dependencies
bash
npm install @supabase/supabase-js @supabase/ssr drizzle-orm postgres
npm install -D drizzle-kit
Environment Variables
bash
# .env.local
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=<from supabase start>

# Server-side only
SUPABASE_SERVICE_ROLE_KEY=<from supabase start>
DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres

Drizzle Setup

drizzle.config.ts
typescript
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './supabase/migrations',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
  schemaFilter: ['public'],
});
src/db/index.ts
typescript
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

const client = postgres(process.env.DATABASE_URL!, {
  prepare: false, // Required for Supabase connection pooling
});

export const db = drizzle(client, { schema });
src/db/schema.ts
typescript
import {
  pgTable,
  uuid,
  text,
  timestamp,
  boolean,
} from 'drizzle-orm/pg-core';

export const profiles = pgTable('profiles', {
  id: uuid('id').primaryKey(), // References auth.users
  email: text('email').notNull(),
  name: text('name'),
  avatarUrl: text('avatar_url'),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

export const posts = pgTable('posts', {
  id: uuid('id').primaryKey().defaultRandom(),
  authorId: uuid('author_id').references(() => profiles.id).notNull(),
  title: text('title').notNull(),
  content: text('content'),
  published: boolean('published').default(false),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

// Type exports
export type Profile = typeof profiles.$inferSelect;
export type NewProfile = typeof profiles.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;

Supabase Clients

src/lib/supabase/client.ts (Browser)
typescript
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
src/lib/supabase/server.ts (Server Components/Actions)
typescript
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Called from Server Component - ignore
          }
        },
      },
    }
  );
}
src/lib/supabase/middleware.ts (For Middleware)
typescript
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value }) =>
            request.cookies.set(name, value)
          );
          supabaseResponse = NextResponse.next({ request });
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          );
        },
      },
    }
  );

  // Refresh session
  const { data: { user } } = await supabase.auth.getUser();

  return { supabaseResponse, user };
}

Middleware

src/middleware.ts
typescript
import { type NextRequest, NextResponse } from 'next/server';
import { updateSession } from '@/lib/supabase/middleware';

const publicRoutes = ['/', '/login', '/signup', '/auth/callback'];

export async function middleware(request: NextRequest) {
  const { supabaseResponse, user } = await updateSession(request);

  const isPublicRoute = publicRoutes.some(route =>
    request.nextUrl.pathname.startsWith(route)
  );

  // Redirect unauthenticated users to login
  if (!user && !isPublicRoute) {
    const url = request.nextUrl.clone();
    url.pathname = '/login';
    url.searchParams.set('redirectTo', request.nextUrl.pathname);
    return NextResponse.redirect(url);
  }

  // Redirect authenticated users away from auth pages
  if (user && (request.nextUrl.pathname === '/login' || request.nextUrl.pathname === '/signup')) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return supabaseResponse;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Auth Helpers

src/lib/auth.ts
typescript
import { redirect } from 'next/navigation';
import { createClient } from '@/lib/supabase/server';

export async function getUser() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  return user;
}

export async function requireAuth() {
  const user = await getUser();
  if (!user) {
    redirect('/login');
  }
  return user;
}

export async function requireGuest() {
  const user = await getUser();
  if (user) {
    redirect('/dashboard');
  }
}

Auth Pages

src/app/(auth)/login/page.tsx
typescript
import { requireGuest } from '@/lib/auth';
import { LoginForm } from '@/components/auth/login-form';

export default async function LoginPage() {
  await requireGuest();

  return (
    <div className="flex min-h-screen items-center justify-center">
      <LoginForm />
    </div>
  );
}
src/components/auth/login-form.tsx
typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { createClient } from '@/lib/supabase/client';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    const supabase = createClient();
    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      setError(error.message);
      setLoading(false);
      return;
    }

    router.push('/dashboard');
    router.refresh();
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 w-full max-w-sm">
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>
      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
      </div>
      {error && <p className="text-red-500">{error}</p>}
      <button type="submit" disabled={loading}>
        {loading ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  );
}
src/app/(auth)/callback/route.ts
typescript
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get('code');
  const next = searchParams.get('next') ?? '/dashboard';

  if (code) {
    const supabase = await createClient();
    const { error } = await supabase.auth.exchangeCodeForSession(code);

    if (!error) {
      return NextResponse.redirect(`${origin}${next}`);
    }
  }

  return NextResponse.redirect(`${origin}/login?error=auth_error`);
}

Server Actions

src/app/actions/posts.ts
typescript
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { db } from '@/db';
import { posts, NewPost } from '@/db/schema';
import { requireAuth } from '@/lib/auth';
import { eq } from 'drizzle-orm';

export async function createPost(formData: FormData) {
  const user = await requireAuth();

  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  const [post] = await db.insert(posts).values({
    authorId: user.id,
    title,
    content,
  }).returning();

  revalidatePath('/dashboard');
  redirect(`/posts/${post.id}`);
}

export async function updatePost(id: string, formData: FormData) {
  const user = await requireAuth();

  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  await db.update(posts)
    .set({ title, content })
    .where(eq(posts.id, id));

  revalidatePath(`/posts/${id}`);
}

export async function deletePost(id: string) {
  const user = await requireAuth();

  await db.delete(posts).where(eq(posts.id, id));

  revalidatePath('/dashboard');
  redirect('/dashboard');
}

Data Fetching

src/db/queries/posts.ts
typescript
import { db } from '@/db';
import { posts, profiles } from '@/db/schema';
import { eq, desc, and } from 'drizzle-orm';

export async function getPublishedPosts(limit = 10) {
  return db
    .select({
      id: posts.id,
      title: posts.title,
      content: posts.content,
      author: profiles.name,
      createdAt: posts.createdAt,
    })
    .from(posts)
    .innerJoin(profiles, eq(posts.authorId, profiles.id))
    .where(eq(posts.published, true))
    .orderBy(desc(posts.createdAt))
    .limit(limit);
}

export async function getUserPosts(userId: string) {
  return db
    .select()
    .from(posts)
    .where(eq(posts.authorId, userId))
    .orderBy(desc(posts.createdAt));
}

export async function getPostById(id: string) {
  const [post] = await db
    .select()
    .from(posts)
    .where(eq(posts.id, id))
    .limit(1);

  return post ?? null;
}
In Server Components
typescript
// src/app/dashboard/page.tsx
import { requireAuth } from '@/lib/auth';
import { getUserPosts } from '@/db/queries/posts';

export default async function DashboardPage() {
  const user = await requireAuth();
  const posts = await getUserPosts(user.id);

  return (
    <div>
      <h1>Your Posts</h1>
      {posts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

Storage

Upload Component
typescript
'use client';

import { useState } from 'react';
import { createClient } from '@/lib/supabase/client';

export function AvatarUpload({ userId }: { userId: string }) {
  const [uploading, setUploading] = useState(false);

  const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setUploading(true);
    const supabase = createClient();

    const fileExt = file.name.split('.').pop();
    const filePath = `${userId}/avatar.${fileExt}`;

    const { error } = await supabase.storage
      .from('avatars')
      .upload(filePath, file, { upsert: true });

    if (error) {
      console.error('Upload error:', error);
    }

    setUploading(false);
  };

  return (
    <input
      type="file"
      accept="image/*"
      onChange={handleUpload}
      disabled={uploading}
    />
  );
}
Get Public URL
typescript
import { createClient } from '@/lib/supabase/server';

export async function getAvatarUrl(userId: string) {
  const supabase = await createClient();

  const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl(`${userId}/avatar.png`);

  return data.publicUrl;
}

Realtime

Client Component with Subscription
typescript
'use client';

import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client';
import { Post } from '@/db/schema';

export function RealtimePosts({ initialPosts }: { initialPosts: Post[] }) {
  const [posts, setPosts] = useState(initialPosts);

  useEffect(() => {
    const supabase = createClient();

    const channel = supabase
      .channel('posts')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'posts' },
        (payload) => {
          if (payload.eventType === 'INSERT') {
            setPosts((prev) => [payload.new as Post, ...prev]);
          } else if (payload.eventType === 'DELETE') {
            setPosts((prev) => prev.filter((p) => p.id !== payload.old.id));
          } else if (payload.eventType === 'UPDATE') {
            setPosts((prev) =>
              prev.map((p) => (p.id === payload.new.id ? payload.new as Post : p))
            );
          }
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, []);

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

OAuth Providers

src/components/auth/oauth-buttons.tsx
typescript
'use client';

import { createClient } from '@/lib/supabase/client';

export function OAuthButtons() {
  const handleOAuth = async (provider: 'google' | 'github') => {
    const supabase = createClient();

    await supabase.auth.signInWithOAuth({
      provider,
      options: {
        redirectTo: `${window.location.origin}/auth/callback`,
      },
    });
  };

  return (
    <div className="space-y-2">
      <button onClick={() => handleOAuth('google')}>
        Continue with Google
      </button>
      <button onClick={() => handleOAuth('github')}>
        Continue with GitHub
      </button>
    </div>
  );
}

Sign Out

Server Action
typescript
// src/app/actions/auth.ts
'use server';

import { redirect } from 'next/navigation';
import { createClient } from '@/lib/supabase/server';

export async function signOut() {
  const supabase = await createClient();
  await supabase.auth.signOut();
  redirect('/login');
}
Sign Out Button
typescript
'use client';

import { signOut } from '@/app/actions/auth';

export function SignOutButton() {
  return (
    <form action={signOut}>
      <button type="submit">Sign Out</button>
    </form>
  );
}

Anti-Patterns

  • Using Supabase client for DB queries - Use Drizzle for type-safety
  • Fetching in client components - Prefer server components
  • Not using middleware for auth - Session refresh is critical
  • Calling cookies() synchronously - Must await in Next.js 15+
  • Service key in client - Never expose, server-only
  • Missing revalidatePath - Always revalidate after mutations
  • Not handling auth errors - Show user-friendly messages

© alinaqi, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/supabase-nextjs of alinaqi/maggy.

Open the folder on GitHubat commit 72a456e

Compare with similar skills

Supabase Nextjs next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Supabase Nextjs compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Supabase Nextjs this skillalinaqi/maggy707—~4.3kAutomated safety check: NotesMIT
Context7 MCPrtadewald/skills185—~681Automated safety check: PassNone
Documentation Lookupaffaan-m/ECC276k1 repos~670Automated safety check: PassMIT
Documentation Lookupaffaan-m/ECC276k—~640Automated safety check: PassMIT
ImplementAI-Unified-Process/marketplace142—~4.7kAutomated safety check: WarnApache-2.0
Typescriptericrisco/rsc-harness180—~2.8kAutomated safety check: PassMIT

Similar skills

  • Context7 MCP

    rtadewald/skills

    This skill should be used when the user asks about libraries, frameworks, API references, or needs code examples.

    185 GitHub stars~681 tokensUpdated 11 days ago
    DatabasesAuto-check passed
  • 通过 Context7 MCP 使用最新的库和框架文档,而非训练数据。当用户提出设置问题、API参考、代码示例或命名框架(例如 React、Next.js、Prisma)时激活。

    276k GitHub starsUsed in 1 repo~670 tokens
    DatabasesAuto-check passed
  • 訓練データの代わりにContext7 MCP経由で最新のライブラリとフレームワークドキュメント使用。セットアップの質問、APIリファレンス、コード例、またはユーザーがフレームワーク(例:React、Next.js、Prisma)に名前を付けるときにアクティベーション。

    276k GitHub stars~640 tokensUpdated today
    DatabasesAuto-check passed
  • Implement

    AI-Unified-Process/marketplace

    Implements use cases across a NestJS backend with Drizzle ORM over PostgreSQL and a Next.js App Router frontend wired to that API.

    142 GitHub stars~4.7k tokensUpdated 5 days ago
    DatabasesAuto-check: warnings
  • Typescript

    ericrisco/rsc-harness

    A skill your agent uses when writing or fixing TypeScript type code (.ts/.tsx/.d.ts), modeling data with generics/unions/branded types, diagnosing narrowing failures, or configuring tsconfig and the…

    180 GitHub stars~2.8k tokensUpdated today
    DatabasesAuto-check passed
  • Documentation Lookup

    KaimingWan/oh-my-kiro

    Fetch current library/framework documentation via Context7. An agent skill from KaimingWan/oh-my-kiro.

    107 GitHub stars~616 tokensUpdated 6 mo ago
    Backend & APIsAuto-check passed

More from alinaqi/maggy

All 71 skills in this repo
  • Aeo Optimization

    alinaqi/maggy

    AI Engine Optimization - semantic triples, page templates, content clusters for AI citations

    707 GitHub stars~3.7k tokensUpdated 16 days ago
    Auto-check passed
  • Agent Teams

    alinaqi/maggy

    Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement

    707 GitHub stars~5k tokensUpdated 16 days ago
    Auto-check: notes
  • AI Models

    alinaqi/maggy

    Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate

    707 GitHub stars~4.1k tokensUpdated 16 days ago
    Auto-check passed
  • Android Java

    alinaqi/maggy

    Android Java development with MVVM, ViewBinding, and Espresso testing

    707 GitHub stars~3.9k tokensUpdated 16 days ago
    Auto-check: notes
  • Android Kotlin

    alinaqi/maggy

    Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

    707 GitHub stars~3k tokensUpdated 16 days ago
    Auto-check passed
  • Autonomous Testing

    alinaqi/maggy

    AI-driven testing agent that auto-discovers, generates, executes, evaluates, and fixes tests for any project type

    707 GitHub stars~1.1k tokensUpdated 16 days ago
    Auto-check passed

Categories

Questions about Supabase Nextjs

How do I install Supabase Nextjs in Claude Code?

Run `npx skills add alinaqi/maggy --skill supabase-nextjs -a claude-code`. Or copy the skill folder (skills/supabase-nextjs in alinaqi/maggy) into .claude/skills/supabase-nextjs in your project. Claude Code loads it when a task matches its description.

How do I install Supabase Nextjs in Codex?

Run `npx skills add alinaqi/maggy --skill supabase-nextjs -a codex`. Or copy the skill folder (skills/supabase-nextjs in alinaqi/maggy) into .agents/skills/supabase-nextjs in your project. Codex loads it when a task matches its description.

Can I use Supabase Nextjs in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add alinaqi/maggy --skill supabase-nextjs -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/supabase-nextjs, .gemini/skills/supabase-nextjs, .github/skills/supabase-nextjs and .opencode/skills/supabase-nextjs in your project.

What does Supabase Nextjs need to run?

Going by SKILL.md and its folder, Supabase Nextjs needs the command-line tools its instructions call (npm and supabase) and credentials named NEXT_PUBLIC_SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY.

Does Supabase Nextjs access the network?

SKILL.md names 1 domain. As links in the text: supabase.com. This is read from the text; nothing was executed.

Is Supabase Nextjs safe to install?

Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Supabase Nextjs use?

Supabase Nextjs is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Supabase Nextjs use?

About 4.3k tokens (SKILL.md is roughly 17k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Supabase Nextjs?

Skills that share tags, products or a category with Supabase Nextjs: Context7 MCP (rtadewald/skills, 185 stars), Documentation Lookup (affaan-m/ECC, 276k stars), Documentation Lookup (affaan-m/ECC, 276k stars) and Implement (AI-Unified-Process/marketplace, 142 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Supabase Nextjs?

alinaqi (a GitHub user) maintains it in alinaqi/maggy, which has 707 GitHub stars. The repository holds 71 skills in this directory. The repository was last updated on September 24, 2026.

Source: alinaqi/maggy on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.