Skip to content

Commit 467f24f

Browse files
committed
test: add integration tests for next js
1 parent a7fe11a commit 467f24f

21 files changed

+14489
-0
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ jobs:
8484
npm run test:integration || npm run test:integration
8585
npm run test:integration:browser
8686
87+
- name: Next.js Integration Tests
88+
run: |
89+
cd test/integration/next
90+
pnpm install
91+
pnpm run test
92+
8793
- name: Stop Supabase
8894
run: |
8995
supabase stop

test/integration/next/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Update these with your Supabase details from your project settings > API
2+
# https://app.supabase.com/project/_/settings/api
3+
NEXT_PUBLIC_SUPABASE_URL=your-project-url
4+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

test/integration/next/.gitignore

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*.local
35+
.env
36+
37+
# vercel
38+
.vercel
39+
40+
# typescript
41+
*.tsbuildinfo
42+
next-env.d.ts

test/integration/next/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<a href="https://demo-nextjs-with-supabase.vercel.app/">
2+
<img alt="Next.js and Supabase Starter Kit - the fastest way to build apps with Next.js and Supabase" src="https://demo-nextjs-with-supabase.vercel.app/opengraph-image.png">
3+
<h1 align="center">Next.js and Supabase Starter Kit</h1>
4+
</a>
5+
6+
<p align="center">
7+
The fastest way to build apps with Next.js and Supabase
8+
</p>
9+
10+
<p align="center">
11+
<a href="#features"><strong>Features</strong></a> ·
12+
<a href="#demo"><strong>Demo</strong></a> ·
13+
<a href="#deploy-to-vercel"><strong>Deploy to Vercel</strong></a> ·
14+
<a href="#clone-and-run-locally"><strong>Clone and run locally</strong></a> ·
15+
<a href="#feedback-and-issues"><strong>Feedback and issues</strong></a>
16+
<a href="#more-supabase-examples"><strong>More Examples</strong></a>
17+
</p>
18+
<br/>
19+
20+
## Features
21+
22+
- Works across the entire [Next.js](https://nextjs.org) stack
23+
- App Router
24+
- Pages Router
25+
- Middleware
26+
- Client
27+
- Server
28+
- It just works!
29+
- supabase-ssr. A package to configure Supabase Auth to use cookies
30+
- Password-based authentication block installed via the [Supabase UI Library](https://supabase.com/ui/docs/nextjs/password-based-auth)
31+
- Styling with [Tailwind CSS](https://tailwindcss.com)
32+
- Components with [shadcn/ui](https://ui.shadcn.com/)
33+
- Optional deployment with [Supabase Vercel Integration and Vercel deploy](#deploy-your-own)
34+
- Environment variables automatically assigned to Vercel project
35+
36+
## Demo
37+
38+
You can view a fully working demo at [demo-nextjs-with-supabase.vercel.app](https://demo-nextjs-with-supabase.vercel.app/).
39+
40+
## Deploy to Vercel
41+
42+
Vercel deployment will guide you through creating a Supabase account and project.
43+
44+
After installation of the Supabase integration, all relevant environment variables will be assigned to the project so the deployment is fully functioning.
45+
46+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&project-name=nextjs-with-supabase&repository-name=nextjs-with-supabase&demo-title=nextjs-with-supabase&demo-description=This+starter+configures+Supabase+Auth+to+use+cookies%2C+making+the+user%27s+session+available+throughout+the+entire+Next.js+app+-+Client+Components%2C+Server+Components%2C+Route+Handlers%2C+Server+Actions+and+Middleware.&demo-url=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2F&external-id=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&demo-image=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2Fopengraph-image.png)
47+
48+
The above will also clone the Starter kit to your GitHub, you can clone that locally and develop locally.
49+
50+
If you wish to just develop locally and not deploy to Vercel, [follow the steps below](#clone-and-run-locally).
51+
52+
## Clone and run locally
53+
54+
1. You'll first need a Supabase project which can be made [via the Supabase dashboard](https://database.new)
55+
56+
2. Create a Next.js app using the Supabase Starter template npx command
57+
58+
```bash
59+
npx create-next-app --example with-supabase with-supabase-app
60+
```
61+
62+
```bash
63+
yarn create next-app --example with-supabase with-supabase-app
64+
```
65+
66+
```bash
67+
pnpm create next-app --example with-supabase with-supabase-app
68+
```
69+
70+
3. Use `cd` to change into the app's directory
71+
72+
```bash
73+
cd with-supabase-app
74+
```
75+
76+
4. Rename `.env.example` to `.env.local` and update the following:
77+
78+
```
79+
NEXT_PUBLIC_SUPABASE_URL=[INSERT SUPABASE PROJECT URL]
80+
NEXT_PUBLIC_SUPABASE_ANON_KEY=[INSERT SUPABASE PROJECT API ANON KEY]
81+
```
82+
83+
Both `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` can be found in [your Supabase project's API settings](https://supabase.com/dashboard/project/_?showConnect=true)
84+
85+
5. You can now run the Next.js local development server:
86+
87+
```bash
88+
npm run dev
89+
```
90+
91+
The starter kit should now be running on [localhost:3000](http://localhost:3000/).
92+
93+
6. This template comes with the default shadcn/ui style initialized. If you instead want other ui.shadcn styles, delete `components.json` and [re-install shadcn/ui](https://ui.shadcn.com/docs/installation/next)
94+
95+
> Check out [the docs for Local Development](https://supabase.com/docs/guides/getting-started/local-development) to also run Supabase locally.
96+
97+
## Feedback and issues
98+
99+
Please file feedback and issues over on the [Supabase GitHub org](https://github.com/supabase/supabase/issues/new/choose).
100+
101+
## More Supabase examples
102+
103+
- [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments)
104+
- [Cookie-based Auth and the Next.js 13 App Router (free course)](https://youtube.com/playlist?list=PL5S4mPUpp4OtMhpnp93EFSo42iQ40XjbF)
105+
- [Supabase Auth and the Next.js App Router](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs)

test/integration/next/app/page.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { createClient } from '@/lib/supabase/client'
5+
6+
export default function Home() {
7+
const supabase = createClient()
8+
const [realtimeStatus, setRealtimeStatus] = useState<string | null>(null)
9+
const channel = supabase.channel('realtime:public:test')
10+
11+
useEffect(() => {
12+
channel.subscribe((status) => setRealtimeStatus(status))
13+
14+
return () => {
15+
channel.unsubscribe()
16+
}
17+
}, [])
18+
19+
return <div data-testid="realtime_status">{realtimeStatus}</div>
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": true,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "app/globals.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { dirname } from 'path'
2+
import { fileURLToPath } from 'url'
3+
import { FlatCompat } from '@eslint/eslintrc'
4+
5+
const __filename = fileURLToPath(import.meta.url)
6+
const __dirname = dirname(__filename)
7+
8+
const compat = new FlatCompat({
9+
baseDirectory: __dirname,
10+
})
11+
12+
const eslintConfig = [...compat.extends('next/core-web-vitals', 'next/typescript')]
13+
14+
export default eslintConfig
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createBrowserClient } from '@supabase/ssr'
2+
3+
export function createClient() {
4+
return createBrowserClient(
5+
process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://127.0.0.1:54321',
6+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
7+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'
8+
)
9+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { createServerClient } from '@supabase/ssr'
2+
import { NextResponse, type NextRequest } from 'next/server'
3+
import { hasEnvVars } from '../utils'
4+
5+
export async function updateSession(request: NextRequest) {
6+
let supabaseResponse = NextResponse.next({
7+
request,
8+
})
9+
10+
// If the env vars are not set, skip middleware check. You can remove this once you setup the project.
11+
if (!hasEnvVars) {
12+
return supabaseResponse
13+
}
14+
15+
const supabase = createServerClient(
16+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
17+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
18+
{
19+
cookies: {
20+
getAll() {
21+
return request.cookies.getAll()
22+
},
23+
setAll(cookiesToSet) {
24+
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
25+
supabaseResponse = NextResponse.next({
26+
request,
27+
})
28+
cookiesToSet.forEach(({ name, value, options }) =>
29+
supabaseResponse.cookies.set(name, value, options)
30+
)
31+
},
32+
},
33+
}
34+
)
35+
36+
// Do not run code between createServerClient and
37+
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
38+
// issues with users being randomly logged out.
39+
40+
// IMPORTANT: DO NOT REMOVE auth.getUser()
41+
42+
const {
43+
data: { user },
44+
} = await supabase.auth.getUser()
45+
46+
if (
47+
request.nextUrl.pathname !== '/' &&
48+
!user &&
49+
!request.nextUrl.pathname.startsWith('/login') &&
50+
!request.nextUrl.pathname.startsWith('/auth')
51+
) {
52+
// no user, potentially respond by redirecting the user to the login page
53+
const url = request.nextUrl.clone()
54+
url.pathname = '/auth/login'
55+
return NextResponse.redirect(url)
56+
}
57+
58+
// IMPORTANT: You *must* return the supabaseResponse object as it is.
59+
// If you're creating a new response object with NextResponse.next() make sure to:
60+
// 1. Pass the request in it, like so:
61+
// const myNewResponse = NextResponse.next({ request })
62+
// 2. Copy over the cookies, like so:
63+
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
64+
// 3. Change the myNewResponse object to fit your needs, but avoid changing
65+
// the cookies!
66+
// 4. Finally:
67+
// return myNewResponse
68+
// If this is not done, you may be causing the browser and server to go out
69+
// of sync and terminate the user's session prematurely!
70+
71+
return supabaseResponse
72+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createServerClient } from '@supabase/ssr'
2+
import { cookies } from 'next/headers'
3+
4+
export async function createClient() {
5+
const cookieStore = await cookies()
6+
7+
return createServerClient(
8+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
9+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10+
{
11+
cookies: {
12+
getAll() {
13+
return cookieStore.getAll()
14+
},
15+
setAll(cookiesToSet) {
16+
try {
17+
cookiesToSet.forEach(({ name, value, options }) =>
18+
cookieStore.set(name, value, options)
19+
)
20+
} catch {
21+
// The `setAll` method was called from a Server Component.
22+
// This can be ignored if you have middleware refreshing
23+
// user sessions.
24+
}
25+
},
26+
},
27+
}
28+
)
29+
}

0 commit comments

Comments
 (0)