Quickstart: Next.js
Intro#
This example provides the steps to build a basic user management app. It includes:
- Supabase Database: a Postgres database for storing your user data.
- Supabase Auth: users can sign in with magic links (no passwords, only email).
- Supabase Storage: users can upload a photo.
- Row Level Security: data is protected so that individuals can only access their own data.
- Instant APIs: APIs will be automatically generated when you create your database tables.
By the end of this guide you'll have an app which allows users to login and update some basic profile details:
Video Guide#
GitHub#
Should you get stuck while working through the guide, refer to this repo.
Project set up#
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase and then creating a "schema" inside the database.
Create a project#
- Go to app.supabase.com.
- Click on "New Project".
- Enter your project details.
- Wait for the new database to launch.
Set up the database schema#
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor, or you can just copy/paste the SQL from below and run it yourself.
- Go to the SQL Editor page in the Dashboard.
- Click User Management Starter.
- Click Run.
Get the API Keys#
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
We just need to get the URL and anon
key from the API settings.
- Go to the Settings page in the Dashboard.
- Click API in the sidebar.
- Find your API
URL
,anon
, andservice_role
keys on this page.
Building the App#
Let's start building the Next.js app from scratch.
Initialize a Next.js app#
We can use create-next-app
to initialize
an app called supabase-nextjs
:
npx create-next-app@latest --use-npm supabase-nextjs cd supabase-nextjs
Then install the Supabase client library: supabase-js
npm install @supabase/supabase-js
And finally we want to save the environment variables in a .env.local
.
All we need are the API URL and the anon
key that you copied earlier.
.env.localNEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
And one optional step is to update the CSS file styles/globals.css
to make the app look nice.
You can find the full contents of this file here.
Set up a Login component#
Supabase Auth Helpers
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
It can be challenging to authenticate your users in all these different environments, that's why we've created the Supabase Auth Helpers to make user management and data fetching within Next.js as easy as possible.
Install the auth helpers for React and Next.js
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
Wrap your pages/_app.js
component with the SessionContextProvider
component:
pages/_app.js1import '../styles/globals.css'
2import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
3import { SessionContextProvider } from '@supabase/auth-helpers-react'
4
5function MyApp({ Component, pageProps }) {
6 const [supabase] = useState(() => createBrowserSupabaseClient())
7
8 return (
9 <SessionContextProvider
10 supabaseClient={supabase}
11 initialSession={pageProps.initialSession}
12 >
13 <Component {...pageProps} />
14 </SessionContextProvider>
15 )
16}
17export default MyApp
Supabase Auth UI
We can use the Supabase Auth UI a pre-built React component for authenticating users via OAuth, email, and magic links.
Install the Supabase Auth UI for React
npm install @supabase/auth-ui-react
Add the Auth
component to your home page
pages/index.js1import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
2import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
3
4const Home = () => {
5 const session = useSession()
6 const supabase = useSupabaseClient()
7
8 return (
9 <div className="container" style={{ padding: '50px 0 100px 0' }}>
10 {!session ? (
11 <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
12 ) : (
13 <p>Account page will go here.</p>
14 )}
15 </div>
16 )
17}
18
19export default Home
Account page#
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called Account.js
within a components
folder.
components/Account.js1import { useState, useEffect } from 'react'
2import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
3
4export default function Account({ session }) {
5 const supabase = useSupabaseClient()
6 const user = useUser()
7 const [loading, setLoading] = useState(true)
8 const [username, setUsername] = useState(null)
9 const [website, setWebsite] = useState(null)
10 const [avatar_url, setAvatarUrl] = useState(null)
11
12 useEffect(() => {
13 getProfile()
14 }, [session])
15
16 async function getProfile() {
17 try {
18 setLoading(true)
19
20 let { data, error, status } = await supabase
21 .from('profiles')
22 .select(`username, website, avatar_url`)
23 .eq('id', user.id)
24 .single()
25
26 if (error && status !== 406) {
27 throw error
28 }
29
30 if (data) {
31 setUsername(data.username)
32 setWebsite(data.website)
33 setAvatarUrl(data.avatar_url)
34 }
35 } catch (error) {
36 alert('Error loading user data!')
37 console.log(error)
38 } finally {
39 setLoading(false)
40 }
41 }
42
43 async function updateProfile({ username, website, avatar_url }) {
44 try {
45 setLoading(true)
46
47 const updates = {
48 id: user.id,
49 username,
50 website,
51 avatar_url,
52 updated_at: new Date().toISOString(),
53 }
54
55 let { error } = await supabase.from('profiles').upsert(updates)
56 if (error) throw error
57 alert('Profile updated!')
58 } catch (error) {
59 alert('Error updating the data!')
60 console.log(error)
61 } finally {
62 setLoading(false)
63 }
64 }
65
66 return (
67 <div className="form-widget">
68 <div>
69 <label htmlFor="email">Email</label>
70 <input id="email" type="text" value={session.user.email} disabled />
71 </div>
72 <div>
73 <label htmlFor="username">Username</label>
74 <input
75 id="username"
76 type="text"
77 value={username || ''}
78 onChange={(e) => setUsername(e.target.value)}
79 />
80 </div>
81 <div>
82 <label htmlFor="website">Website</label>
83 <input
84 id="website"
85 type="website"
86 value={website || ''}
87 onChange={(e) => setWebsite(e.target.value)}
88 />
89 </div>
90
91 <div>
92 <button
93 className="button primary block"
94 onClick={() => updateProfile({ username, website, avatar_url })}
95 disabled={loading}
96 >
97 {loading ? 'Loading ...' : 'Update'}
98 </button>
99 </div>
100
101 <div>
102 <button className="button block" onClick={() => supabase.auth.signOut()}>
103 Sign Out
104 </button>
105 </div>
106 </div>
107 )
108}
Launch!#
Now that we have all the components in place, let's update pages/index.js
:
pages/index.js1import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
2import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
3import Account from '../components/Account'
4
5const Home = () => {
6 const session = useSession()
7 const supabase = useSupabaseClient()
8
9 return (
10 <div className="container" style={{ padding: '50px 0 100px 0' }}>
11 {!session ? (
12 <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
13 ) : (
14 <Account session={session} />
15 )}
16 </div>
17 )
18}
19
20export default Home
Once that's done, run this in a terminal window:
npm run dev
And then open the browser to localhost:3000 and you should see the completed app.
Bonus: Profile photos#
Every Supabase project is configured with Storage for managing large files like photos and videos.
Create an upload widget#
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
components/Avatar.js1import React, { useEffect, useState } from 'react'
2import { useSupabaseClient } from '@supabase/auth-helpers-react'
3
4export default function Avatar({ uid, url, size, onUpload }) {
5 const supabase = useSupabaseClient()
6 const [avatarUrl, setAvatarUrl] = useState(null)
7 const [uploading, setUploading] = useState(false)
8
9 useEffect(() => {
10 if (url) downloadImage(url)
11 }, [url])
12
13 async function downloadImage(path) {
14 try {
15 const { data, error } = await supabase.storage.from('avatars').download(path)
16 if (error) {
17 throw error
18 }
19 const url = URL.createObjectURL(data)
20 setAvatarUrl(url)
21 } catch (error) {
22 console.log('Error downloading image: ', error)
23 }
24 }
25
26 const uploadAvatar = async (event) => {
27 try {
28 setUploading(true)
29
30 if (!event.target.files || event.target.files.length === 0) {
31 throw new Error('You must select an image to upload.')
32 }
33
34 const file = event.target.files[0]
35 const fileExt = file.name.split('.').pop()
36 const fileName = `${uid}.${fileExt}`
37 const filePath = `${fileName}`
38
39 let { error: uploadError } = await supabase.storage
40 .from('avatars')
41 .upload(filePath, file, { upsert: true })
42
43 if (uploadError) {
44 throw uploadError
45 }
46
47 onUpload(filePath)
48 } catch (error) {
49 alert('Error uploading avatar!')
50 console.log(error)
51 } finally {
52 setUploading(false)
53 }
54 }
55
56 return (
57 <div>
58 {avatarUrl ? (
59 <img
60 src={avatarUrl}
61 alt="Avatar"
62 className="avatar image"
63 style={{ height: size, width: size }}
64 />
65 ) : (
66 <div className="avatar no-image" style={{ height: size, width: size }} />
67 )}
68 <div style={{ width: size }}>
69 <label className="button primary block" htmlFor="single">
70 {uploading ? 'Uploading ...' : 'Upload'}
71 </label>
72 <input
73 style={{
74 visibility: 'hidden',
75 position: 'absolute',
76 }}
77 type="file"
78 id="single"
79 accept="image/*"
80 onChange={uploadAvatar}
81 disabled={uploading}
82 />
83 </div>
84 </div>
85 )
86}
Add the new widget#
And then we can add the widget to the Account page:
components/Account.js1// Import the new component 2import Avatar from './Avatar' 3 4// ... 5 6return ( 7 <div className="form-widget"> 8 {/* Add to the body */} 9 <Avatar 10 uid={user.id} 11 url={avatar_url} 12 size={150} 13 onUpload={(url) => { 14 setAvatarUrl(url) 15 updateProfile({ username, website, avatar_url: url }) 16 }} 17 /> 18 {/* ... */} 19 </div> 20)
Next steps#
At this stage you have a fully functional application!
- See the complete example on GitHub and deploy it to Vercel.
- Explore the pre-built Auth UI for React.
- Explore the Auth Helpers for Next.js.
- Explore the Supabase Cache Helpers.
- See the Next.js Subscription Payments Starter template on GitHub.
- Got a question? Ask here.
- Sign in: app.supabase.com