9 | Welcome to Next.js 13! 10 |
11 | 12 |
13 | Get started by editing{' '}
14 | app/page.tsx
15 |
├── .env.local.example
├── .eslintrc.json
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── app
├── client-side
│ └── page.tsx
├── globals.css
├── head.tsx
├── layout.tsx
├── page.module.css
├── page.tsx
├── realtime
│ ├── page.tsx
│ └── realtime-posts.tsx
├── server-rendered
│ ├── [id]
│ │ └── page.tsx
│ └── page.tsx
├── static-with-revalidation
│ ├── [id]
│ │ └── page.tsx
│ └── page.tsx
└── static
│ ├── [id]
│ └── page.tsx
│ └── page.tsx
├── next.config.js
├── package-lock.json
├── package.json
├── pages
└── api
│ └── hello.ts
├── public
├── favicon.ico
└── vercel.svg
├── tsconfig.json
└── utils
└── supabase.ts
/.env.local.example:
--------------------------------------------------------------------------------
1 | # these values can be found in your project's API settings
2 | # https://app.supabase.com/project/_/settings/api
3 |
4 | NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
5 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is an example repo to show how to fetch and cache Supabase data with Next.js Server Components.
2 |
3 | Check out [the article](https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components) to learn more.
4 |
--------------------------------------------------------------------------------
/app/client-side/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import supabase from "../../utils/supabase";
5 |
6 | export default function ClientPosts() {
7 | const [isLoading, setIsLoading] = useState(true);
8 | const [posts, setPosts] = useState Loading{JSON.stringify(posts, null, 2)}
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/head.tsx:
--------------------------------------------------------------------------------
1 | export default function Head() {
2 | return (
3 | <>
4 |
13 | Get started by editing{' '}
14 | app/page.tsx
15 |
{JSON.stringify(posts, null, 2)}; 36 | } 37 | -------------------------------------------------------------------------------- /app/server-rendered/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import supabase from "../../../utils/supabase"; 2 | import { notFound } from "next/navigation"; 3 | 4 | // do not cache this page 5 | export const revalidate = 0; 6 | 7 | export async function generateStaticParams() { 8 | const { data: posts } = await supabase.from("posts").select("id"); 9 | 10 | return posts?.map(({ id }) => ({ 11 | id, 12 | })); 13 | } 14 | 15 | export default async function Post({ 16 | params: { id }, 17 | }: { 18 | params: { id: string }; 19 | }) { 20 | const { data: post } = await supabase 21 | .from("posts") 22 | .select() 23 | .match({ id }) 24 | .single(); 25 | 26 | if (!post) { 27 | notFound(); 28 | } 29 | 30 | return
{JSON.stringify(post, null, 2)}; 31 | } 32 | -------------------------------------------------------------------------------- /app/server-rendered/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import supabase from "../../utils/supabase"; 3 | 4 | // do not cache this page 5 | export const revalidate = 0; 6 | 7 | export default async function Posts() { 8 | const { data: posts } = await supabase.from("posts").select("id, title"); 9 | 10 | if (!posts) { 11 | return
No posts found.
; 12 | } 13 | 14 | return posts.map((post) => ( 15 |16 | {post.title} 17 |
18 | )); 19 | } 20 | -------------------------------------------------------------------------------- /app/static-with-revalidation/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import supabase from "../../../utils/supabase"; 2 | import { notFound } from "next/navigation"; 3 | 4 | // cache this page for 1 minute 5 | export const revalidate = 60; 6 | 7 | export async function generateStaticParams() { 8 | const { data: posts } = await supabase.from("posts").select("id"); 9 | 10 | return posts?.map(({ id }) => ({ 11 | id, 12 | })); 13 | } 14 | 15 | export default async function Post({ 16 | params: { id }, 17 | }: { 18 | params: { id: string }; 19 | }) { 20 | const { data: post } = await supabase 21 | .from("posts") 22 | .select() 23 | .match({ id }) 24 | .single(); 25 | 26 | if (!post) { 27 | notFound(); 28 | } 29 | 30 | return{JSON.stringify(post, null, 2)}; 31 | } 32 | -------------------------------------------------------------------------------- /app/static-with-revalidation/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import supabase from "../../utils/supabase"; 3 | 4 | // cache this page for 1 minute 5 | export const revalidate = 60; 6 | 7 | export default async function Posts() { 8 | const { data: posts } = await supabase.from("posts").select("id, title"); 9 | 10 | if (!posts) { 11 | return
No posts found.
; 12 | } 13 | 14 | return posts.map((post) => ( 15 |16 | {post.title} 17 |
18 | )); 19 | } 20 | -------------------------------------------------------------------------------- /app/static/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import supabase from "../../../utils/supabase"; 2 | import { notFound } from "next/navigation"; 3 | 4 | export async function generateStaticParams() { 5 | const { data: posts } = await supabase.from("posts").select("id"); 6 | 7 | return posts?.map(({ id }) => ({ 8 | id, 9 | })); 10 | } 11 | 12 | export default async function Post({ 13 | params: { id }, 14 | }: { 15 | params: { id: string }; 16 | }) { 17 | const { data: post } = await supabase 18 | .from("posts") 19 | .select() 20 | .match({ id }) 21 | .single(); 22 | 23 | if (!post) { 24 | notFound(); 25 | } 26 | 27 | return{JSON.stringify(post, null, 2)}; 28 | } 29 | -------------------------------------------------------------------------------- /app/static/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import supabase from "../../utils/supabase"; 3 | 4 | export default async function Posts() { 5 | const { data: posts } = await supabase.from("posts").select("id, title"); 6 | 7 | if (!posts) { 8 | return
No posts found.
; 9 | } 10 | 11 | return posts.map((post) => ( 12 |13 | {post.title} 14 |
15 | )); 16 | } 17 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | appDir: true, 5 | }, 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next13", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@supabase/supabase-js": "^2.1.0", 13 | "@types/node": "18.11.9", 14 | "@types/react": "18.0.25", 15 | "@types/react-dom": "18.0.9", 16 | "eslint": "8.27.0", 17 | "eslint-config-next": "13.0.3", 18 | "next": "13.0.3", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "typescript": "4.8.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijonmusters/fetching-and-caching-supabase-data-in-next-js-13-server-components/9b7d19ab165dda7943af5fad7aaf2f1bb423eade/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /utils/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | 3 | export default createClient( 4 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 5 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! 6 | ); 7 | --------------------------------------------------------------------------------