{"id":27122962,"url":"https://github.com/clxrityy/nextjs-supabase","last_synced_at":"2026-05-10T05:02:59.820Z","repository":{"id":285196181,"uuid":"872086650","full_name":"clxrityy/nextjs-supabase","owner":"clxrityy","description":"Next.js \u0026 Supabase realtime updates \u0026 authentication template","archived":false,"fork":false,"pushed_at":"2024-12-17T02:44:31.000Z","size":846,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-07T12:48:46.050Z","etag":null,"topics":["authentication","middleware","nextjs","real-time","realtime","supabase","template"],"latest_commit_sha":null,"homepage":"https://nextjs-supabase-one-kappa.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clxrityy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-13T18:38:05.000Z","updated_at":"2024-10-29T14:57:27.000Z","dependencies_parsed_at":"2025-03-30T07:32:58.032Z","dependency_job_id":"b199645c-0f2a-4878-abfc-b792afd4b55c","html_url":"https://github.com/clxrityy/nextjs-supabase","commit_stats":null,"previous_names":["clxrityy/nextjs-supabase"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/clxrityy/nextjs-supabase","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clxrityy%2Fnextjs-supabase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clxrityy%2Fnextjs-supabase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clxrityy%2Fnextjs-supabase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clxrityy%2Fnextjs-supabase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clxrityy","download_url":"https://codeload.github.com/clxrityy/nextjs-supabase/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clxrityy%2Fnextjs-supabase/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264384304,"owners_count":23599609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","middleware","nextjs","real-time","realtime","supabase","template"],"created_at":"2025-04-07T12:33:21.715Z","updated_at":"2026-05-10T05:02:59.748Z","avatar_url":"https://github.com/clxrityy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nextjs Supabase Auth \u0026 Realtime Template \u003cimg src=\"/app/logo.png\" width=\"30px\" /\u003e\n\n![demo](https://i.gyazo.com/0569610da479b23d504a3c3d978e28f5.png)\n\n## [Demo](https://nextjs-supabase-one-kappa.vercel.app/)\n\n## Features\n\n- [Middleware](#middleware)\n- [Authentication](#authentication)\n  - [Logging in](#logging-in)\n  - [Signing up](#signing-up)\n  - [API auth routes](#api-auth-routes)\n- [Realtime tables](#realtime-tables)\n- [Custom hooks](#custom-hooks)\n\n## Set up\n\n1. Clone the repository (or click use template)\n\n```zsh\ngit clone https://github.com/clxrityy/nextjs-supabase-auth.git\n```\n\n2. Include your `.env` variables\n\n```env\nNEXT_PUBLIC_SUPABASE_URL=\nNEXT_PUBLIC_SUPABASE_ANON_KEY=\nNEXT_PUBLIC_SERVICE_ROLE_KEY=\n```\n\n- Acquire them on the [supabase dashboard](https://supabase.com/dashboard)\n\n\u003e More information about quickstarting Supabse with Nextjs: [Using Supabase with Nextjs](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)\n\n\u003c!-- ### Customization\n\n- Icons used throughout the UI are defined in the [`/config`](/config/index.ts) folder.\n    - From [react-icons](https://react-icons.github.io/react-icons/)\n- Images/logos are generated from [favicon.io](https://favicon.io/favicon-converter/) --\u003e\n\n---\n\n## Middleware\n\nThe middleware will redirect any unauthenticated user to the `/login` page.\n\n```ts\nimport { createMiddlewareClient } from \"@supabase/auth-helpers-nextjs\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nexport default async function middleware(req: NextRequest) {\n  // Response object\n  const res = NextResponse.next();\n\n  // Initialize supabase middleware client\n  const supabase = createMiddlewareClient({ req, res });\n\n  // Deconstruct the session object from supabase auth\n  const {\n    data: { session },\n  } = await supabase.auth.getSession();\n\n  // If no session is present, rewrite the route to /login\n  if (!session) {\n    return NextResponse.rewrite(new URL(\"/login\"), req.url);\n  }\n\n  // Otherwise, return the response\n  return res;\n}\n\nexport const config = {\n  /**\n   * @see https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher\n   */\n  matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n```\n\n\u003e More information about Nextjs middleware: [Routing: Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)\n\n---\n\n## Authentication\n\nOnce the user is redirected to the `/login` route, they're prompted with a form to either login or sign up. There are no external authentication providers (such as Google, Discord, GitHub, etc.) configured currently, but they can easily be set up.\n\nYou must login with your email \u0026 password\n\n![login page](https://i.gyazo.com/765aa68660ee83eb3010769d3dc5cdbe.png)\n\n\u003e **Note:** The form is currently configured so that the email \u0026 password inputs work for logging in OR signing up (rather than having a separate `/sign-up` route).\n\n### Logging in\n\n- The `email` \u0026 `password` values are saved with `useState()` and set with the `handleChange()` function.\n\n```tsx\nconst [data, setData] = useState\u003c{\n  email: string;\n  password: string;\n}\u003e();\n\nconst handleChange = (e: React.ChangeEvent\u003cHTMLInputElement\u003e) =\u003e {\n  const { name, value } = e.target;\n\n  setData((prev: any) =\u003e ({\n    ...prev,\n    [name]: value,\n  }));\n};\n```\n\n- A `\u003cform /\u003e` element is rendered with a `POST` action to `/api/auth/login`.\n\n```tsx\n\u003cform action={\"/api/auth/login\"} method=\"post\"\u003e\n  \u003cinput type=\"text\" name=\"email\" onChange={handleChange} /\u003e\n  \u003cinput type=\"password\" name=\"password\" /\u003e\n  \u003cButton onClick={login}\u003eLogin\u003c/Button\u003e\n\u003c/form\u003e\n```\n\n- Upon clicking the **Login** button, the `login()` function is called; which will use the data to sign in with supabase auth (and make the post request to the API route).\n\n```tsx\nconst login = async () =\u003e {\n  if (data) {\n    try {\n      const { data: authData, error } =\n        await supabaseClient.auth.signInWithPassword({\n          email: data.email,\n          password: data.password,\n        });\n\n      if (error) {\n        console.log(`Error: ${error.message}`);\n      }\n    } catch (e) {\n      console.log(`Error: ${e}`);\n    } finally {\n      redirect(\"/\");\n    }\n  }\n};\n```\n\n### Signing up\n\nAs previously stated, there is no sign up route. The values within the input elements on the login page can be used to sign up as well.\n\n- A separate form is rendered which has a POST action to `/api/auth/sign-up` and the **Sign Up** button will run the `signUp()` function on click.\n\n```tsx\nconst signUp = async () =\u003e {\n  if (data) {\n    try {\n      const { data: authData, error } = await supabaseClient.auth.signUp({\n        email: data.email,\n        password: data.password,\n      });\n\n      if (error) {\n        console.log(`Error: ${error.message}`);\n      }\n    } catch (e) {\n      console.log(`Error: ${e}`);\n    }\n  }\n};\n```\n\n```tsx\n\u003cform\n  className=\"flex flex-col items-center justify-center gap-2\"\n  action={\"/api/auth/sign-up\"}\n  method=\"post\"\n\u003e\n  \u003cp className=\"text-sm lg:text-base font-bold text-gray-700\"\u003e\n    Don\u0026#39;t have an account?\n  \u003c/p\u003e\n  \u003cButton onClick={signUp} className=\"font-semibold\" variant={\"secondary\"}\u003e\n    Sign up\n  \u003c/Button\u003e\n\u003c/form\u003e\n```\n\n### API Auth Routes\n\n#### `/api/auth/login`\n\n- The API route will grab the `formData()` from the request then call supabase route handler client's `auth.signInWithPassword()` passing in the given email \u0026 password from the form data.\n  - [Read about `createRouteHandlerClient()` from `@supabase/auth-helpers-nextjs`](https://supabase.github.io/auth-helpers/functions/nextjs_src.createRouteHandlerClient.html)\n- The response is redirected to the root.\n\n#### `/api/auth/sign-up`\n\n- Also grabs the form data.\n- ALSO utilizes `createRouteHandlerClient()`\n- Calls `auth.signUp()`, passing in the email, password, and email redirect callback.\n\n```ts\n// api/auth/sign-up\nimport { createRouteHandlerClient } from \"@supabase/auth-helpers-nextjs\";\nimport { cookies } from \"next/handlers\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nexport async function POST(req: NextRequest) {\n  // ...\n  const cookieStore = cookies();\n\n  const supabase = createRouteHandlerClient({\n    cookies: () =\u003e cookieStore,\n  });\n\n  try {\n    await supabase.auth.signUp({\n      email,\n      password,\n      options: {\n        emailRedirectTo: `${url.origin}/api/auth/callback`,\n      },\n    });\n  } catch (e) {\n    // ...\n  }\n\n  return NextResponse.redirect(url.origin, {\n    status: 301,\n  });\n}\n```\n\n#### `/api/auth/callback`\n\n- Gets the code from the url search params and turns it into a session.\n  - Read about [sessions](https://supabase.com/docs/guides/auth/sessions) with supabase.\n\n```ts\nconst url = new URL(req.url);\n\nconst code = url.searchParams.get(\"code\");\n\nif (code) {\n  const cookieStore = cookies();\n  const supabase = createRouteHandlerClient({\n    cookies: () =\u003e cookieStore,\n  });\n\n  await supabase.auth.exchangeCodeForSession(code);\n}\n\nreturn NextResponse.redirect(url.origin);\n```\n\n---\n\n## Realtime Tables\n\nIn order to receive live updates, you must:\n\n- Create a table\n![create a table](https://i.gyazo.com/ba0922ea2fc2a936325bbfbb966d2c94.png)\n- Enable realtime\n![enable realtime](https://i.gyazo.com/ab770d738f91850a0e2994b9dfbf3751.png)\n- Create a channel to subscribe to realtime changes within a `useEffect()` hook.\n  - [Subabase | Subscribing to Database Changes](https://supabase.com/docs/guides/realtime/subscribing-to-database-changes)\n\n  ```tsx\n  useEffect(() =\u003e {\n    const channel = supabase.channel(\"whatever you wanna call it\")\n      .on(\"postgres_changes\", {\n        event: \"INSERT\", // listen to whenever a new row is added\n        schema: \"public\",\n        table: \"table-name\"\n      }, (payload) =\u003e {\n        // handle the payload\n      }).subscribe(); // subscribe to the channel\n\n      /**\n       * finally, return a callback function to unsubscribe when the component unmounts\n       */\n      return () =\u003e {\n        channel.unsubscribe(); \n      }\n  }, [])\n  ```\n  \u003e **Note:** Make sure to mark the component with `\"use client\"`\n\n---\n\n## Custom hooks\n\nReact hooks (such as `useState()` \u0026 `useEffect()`) can be utilized to create some custom hooks that will ease the usage of supabase within client components.\n\nFor instance, this live click counter\n\n![click counter](https://i.gyazo.com/03af7279417f84539e33e6d7d84c7edd.gif)\n\nRather than calling the supabase client directly in the component, here's how the hook is set up:\n\n```tsx\n\"use client\"\n\nexport default function useClicks() {\n  const [clicks, setClicks] = useState\u003c{[key: number]: {\n    id: number:\n    created_at: string;\n    userId: string;\n  }}\u003e();\n\n  const getClicks = async () =\u003e {\n    const { data, error } = await supabaseClient\n      .from(\"clicks-table\")\n      .select(\"*\");\n\n    if (data) {\n      setClicks(data);\n    }\n\n    // ...\n  };\n\n  return {\n    getClicks,\n    clicks,\n    // ...\n  }\n}\n```\n\nThen, within a client component, deconstruct the `getClicks()` function from the hook, and call it to retreive all the clicks.\n\n```tsx\n\"use client\"\n\nexport default function Component() {\n  const { clicks, getClicks } = useClicks();\n  // ...\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclxrityy%2Fnextjs-supabase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclxrityy%2Fnextjs-supabase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclxrityy%2Fnextjs-supabase/lists"}