├── authentication-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── app │ │ ├── admin │ │ │ ├── actions.ts │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── sign-in │ │ │ └── [[...sign-in]] │ │ │ │ └── page.tsx │ │ ├── sign-up │ │ │ └── [[...sign-up]] │ │ │ │ └── page.tsx │ │ └── user-profile │ │ │ └── [[...user-profile]] │ │ │ └── page.tsx │ ├── components │ │ ├── counter.tsx │ │ └── navigation.tsx │ └── middleware.ts ├── tailwind.config.ts ├── tsconfig.json └── types │ └── globals.d.ts ├── caching-revalidation-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── db.json ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prisma │ ├── app.db │ ├── migrations │ │ ├── 20241125081652_init │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── json-server-products │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── prisma-products │ │ │ └── page.tsx │ └── prisma-db.ts ├── tailwind.config.ts └── tsconfig.json ├── data-fetching-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prisma │ ├── migrations │ │ ├── 20241119043535_init │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── actions │ │ └── products.ts │ ├── app │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── posts-sequential │ │ │ ├── author.tsx │ │ │ └── page.tsx │ │ ├── products-db-create │ │ │ └── page.tsx │ │ ├── products-db │ │ │ ├── [id] │ │ │ │ ├── page.tsx │ │ │ │ └── product-edit-form.tsx │ │ │ ├── page.tsx │ │ │ └── product-detail.tsx │ │ ├── react-form │ │ │ ├── api │ │ │ │ └── route.ts │ │ │ └── page.tsx │ │ ├── user-parallel │ │ │ └── [userId] │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ ├── users-client │ │ │ └── page.tsx │ │ └── users-server │ │ │ ├── error.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── components │ │ ├── search.tsx │ │ └── submit.tsx │ └── prisma-db.ts ├── tailwind.config.ts └── tsconfig.json ├── hello-world ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ └── app │ │ ├── favicon.ico │ │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx ├── tailwind.config.ts └── tsconfig.json ├── misc-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ └── app │ │ ├── about │ │ ├── about.module.css │ │ ├── about.module.scss │ │ └── page.tsx │ │ ├── colors.scss │ │ ├── contact │ │ ├── contact.module.css │ │ ├── contact.module.scss │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx ├── tailwind.config.ts └── tsconfig.json ├── rendering-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── app │ │ ├── about │ │ │ └── page.tsx │ │ ├── client-route │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── product-reviews │ │ │ └── page.tsx │ │ ├── products │ │ │ ├── [id] │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ └── server-route │ │ │ └── page.tsx │ ├── components │ │ ├── ImageSlider.tsx │ │ ├── product.tsx │ │ ├── reviews.tsx │ │ └── theme-provider.tsx │ └── utils │ │ └── server-utils.ts ├── tailwind.config.ts └── tsconfig.json ├── route-handlers-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ └── app │ │ ├── api │ │ ├── v1 │ │ │ └── users │ │ │ │ └── route.ts │ │ └── v2 │ │ │ └── users │ │ │ └── route.ts │ │ ├── categories │ │ └── route.ts │ │ ├── comments │ │ ├── [id] │ │ │ └── route.ts │ │ ├── data.ts │ │ └── route.ts │ │ ├── dashboard │ │ ├── route.ts │ │ └── users │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── hello │ │ └── route.ts │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── profile │ │ ├── api │ │ │ └── route.ts │ │ └── page.tsx │ │ └── time │ │ └── route.ts ├── tailwind.config.ts └── tsconfig.json └── routing-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── file.svg ├── globe.svg ├── next.svg ├── vercel.svg └── window.svg ├── src ├── app │ ├── (auth) │ │ ├── forgot-password │ │ │ └── page.tsx │ │ ├── login │ │ │ └── page.tsx │ │ ├── register │ │ │ └── page.tsx │ │ ├── styles.css │ │ └── template.tsx │ ├── _lib │ │ ├── format-date.js │ │ └── page.tsx │ ├── about │ │ └── page.tsx │ ├── articles │ │ └── [articleId] │ │ │ └── page.tsx │ ├── blog │ │ ├── first │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── second │ │ │ └── page.tsx │ ├── complex-dashboard │ │ ├── @login │ │ │ └── page.tsx │ │ ├── @notifications │ │ │ ├── archived │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── @revenue │ │ │ ├── default.tsx │ │ │ └── page.tsx │ │ ├── @users │ │ │ ├── default.tsx │ │ │ └── page.tsx │ │ ├── default.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── counter │ │ ├── counter.tsx │ │ └── page.tsx │ ├── dashboard │ │ ├── line-chart.tsx │ │ └── page.tsx │ ├── docs │ │ └── [[...slug]] │ │ │ └── page.tsx │ ├── error-wrapper.tsx │ ├── f1 │ │ ├── (.)f2 │ │ │ └── page.tsx │ │ ├── (..)f3 │ │ │ └── page.tsx │ │ ├── f2 │ │ │ ├── (..)(..)f4 │ │ │ │ └── page.tsx │ │ │ ├── inner-f2 │ │ │ │ ├── (...)f5 │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ └── page.tsx │ ├── f3 │ │ └── page.tsx │ ├── f4 │ │ └── page.tsx │ ├── f5 │ │ └── page.tsx │ ├── global-error.tsx │ ├── globals.css │ ├── layout.tsx │ ├── not-found.tsx │ ├── page.tsx │ ├── photo-feed │ │ ├── @modal │ │ │ ├── (.)[id] │ │ │ │ └── page.tsx │ │ │ └── default.tsx │ │ ├── [id] │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── photos │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── 6.jpg │ │ │ └── 7.jpg │ │ ├── styles.css │ │ └── wonders.ts │ ├── products │ │ ├── [productId] │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── reviews │ │ │ │ └── [reviewId] │ │ │ │ ├── error.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ ├── error.tsx │ │ └── page.tsx │ └── profile │ │ └── page.tsx └── components │ ├── card.tsx │ └── modal.tsx ├── tailwind.config.ts └── tsconfig.json /authentication-demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /authentication-demo/.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.* 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 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /authentication-demo/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /authentication-demo/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /authentication-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authentication-demo", 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 | "@clerk/nextjs": "^6.5.1", 13 | "next": "15.0.3", 14 | "react": "19.0.0-rc-66855b96-20241106", 15 | "react-dom": "19.0.0-rc-66855b96-20241106" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "15.0.3", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /authentication-demo/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /authentication-demo/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication-demo/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication-demo/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication-demo/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication-demo/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication-demo/src/app/admin/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { auth } from "@clerk/nextjs/server"; 3 | import { clerkClient } from "@clerk/nextjs/server"; 4 | import { Roles } from "../../../types/globals"; 5 | import { revalidatePath } from "next/cache"; 6 | 7 | export async function setRole(formData: FormData) { 8 | const { sessionClaims } = await auth(); 9 | 10 | // Check that the user trying to set the role is an admin 11 | if (sessionClaims?.metadata?.role !== "admin") { 12 | throw new Error("Not Authorized"); 13 | } 14 | 15 | const client = await clerkClient(); 16 | const id = formData.get("id") as string; 17 | const role = formData.get("role") as Roles; 18 | 19 | try { 20 | await client.users.updateUser(id, { 21 | publicMetadata: { role }, 22 | }); 23 | revalidatePath("/admin"); 24 | } catch { 25 | throw new Error("Failed to set role"); 26 | } 27 | } 28 | 29 | export async function removeRole(formData: FormData) { 30 | const { sessionClaims } = await auth(); 31 | 32 | if (sessionClaims?.metadata?.role !== "admin") { 33 | throw new Error("Not Authorized"); 34 | } 35 | 36 | const client = await clerkClient(); 37 | const id = formData.get("id") as string; 38 | 39 | try { 40 | await client.users.updateUser(id, { 41 | publicMetadata: { role: null }, 42 | }); 43 | revalidatePath("/admin"); 44 | } catch { 45 | throw new Error("Failed to remove role"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /authentication-demo/src/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import { clerkClient } from "@clerk/nextjs/server"; 2 | import { removeRole, setRole } from "./actions"; 3 | 4 | export default async function Admin() { 5 | const client = await clerkClient(); 6 | 7 | const users = (await client.users.getUserList()).data; 8 | 9 | return ( 10 | <> 11 | {users.map((user) => { 12 | return ( 13 |
21 |
22 |
23 | {user.firstName} {user.lastName} 24 |
25 | 26 |
27 | { 28 | user.emailAddresses.find( 29 | (email) => email.id === user.primaryEmailAddressId 30 | )?.emailAddress 31 | } 32 |
33 | 34 |
35 | {user.publicMetadata.role as string} 36 |
37 |
38 | 39 |
40 |
41 | 42 | 43 | 49 |
50 | 51 |
52 | 53 | 54 | 60 |
61 | 62 |
63 | 64 | 70 |
71 |
72 |
73 | ); 74 | })} 75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /authentication-demo/src/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth, currentUser } from "@clerk/nextjs/server"; 2 | 3 | export default async function DashboardPage() { 4 | const authObj = await auth(); 5 | const userObj = await currentUser(); 6 | 7 | console.log({ authObj }); 8 | console.log({ userObj }); 9 | 10 | return

Dashboard

; 11 | } 12 | -------------------------------------------------------------------------------- /authentication-demo/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/authentication-demo/src/app/favicon.ico -------------------------------------------------------------------------------- /authentication-demo/src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/authentication-demo/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /authentication-demo/src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/authentication-demo/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /authentication-demo/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /authentication-demo/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ClerkProvider } from "@clerk/nextjs"; 2 | import type { Metadata } from "next"; 3 | import localFont from "next/font/local"; 4 | import "./globals.css"; 5 | import { Navigation } from "@/components/navigation"; 6 | 7 | const geistSans = localFont({ 8 | src: "./fonts/GeistVF.woff", 9 | variable: "--font-geist-sans", 10 | weight: "100 900", 11 | }); 12 | const geistMono = localFont({ 13 | src: "./fonts/GeistMonoVF.woff", 14 | variable: "--font-geist-mono", 15 | weight: "100 900", 16 | }); 17 | 18 | export const metadata: Metadata = { 19 | title: "Create Next App", 20 | description: "Generated by create next app", 21 | }; 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode; 27 | }>) { 28 | return ( 29 | 30 | 31 | 34 | 35 | {children} 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /authentication-demo/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { Counter } from "@/components/counter"; 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | Next.js logo 15 | 16 |
    17 |
  1. 18 | Get started by editing{" "} 19 | 20 | src/app/page.tsx 21 | 22 | . 23 |
  2. 24 |
  3. Save and see your changes instantly.
  4. 25 |
26 | 27 |
28 | 34 | Vercel logomark 41 | Deploy now 42 | 43 | 49 | Read our docs 50 | 51 |
52 |
53 | 100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /authentication-demo/src/app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | 3 | export default function SignInPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /authentication-demo/src/app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | 3 | export default function SignUpPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /authentication-demo/src/app/user-profile/[[...user-profile]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { UserProfile } from "@clerk/nextjs"; 2 | 3 | export default function UserProfilePage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /authentication-demo/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { 5 | useAuth, 6 | // useUser 7 | } from "@clerk/nextjs"; 8 | 9 | export const Counter = () => { 10 | const [count, setCount] = useState(0); 11 | const { 12 | isLoaded, 13 | userId, 14 | // sessionId, getToken 15 | } = useAuth(); 16 | // const { isLoaded, isSignedIn, user } = useUser(); 17 | 18 | if (!isLoaded || !userId) { 19 | return null; 20 | } 21 | 22 | // if (!isLoaded || !isSignedIn) { 23 | // return null; 24 | // } 25 | return ( 26 | <> 27 |

Count: {count}

28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /authentication-demo/src/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SignInButton, 3 | SignOutButton, 4 | SignUpButton, 5 | // UserButton, 6 | SignedIn, 7 | SignedOut, 8 | } from "@clerk/nextjs"; 9 | import Link from "next/link"; 10 | export const Navigation = () => { 11 | return ( 12 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /authentication-demo/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; 2 | import { NextResponse } from "next/server"; 3 | 4 | // const isProtectedRoute = createRouteMatcher(["/user-profile"]); 5 | const isPublicRoute = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]); 6 | 7 | const isAdminRoute = createRouteMatcher(["/admin(.*)"]); 8 | 9 | export default clerkMiddleware(async (auth, req) => { 10 | const { userId, redirectToSignIn } = await auth(); 11 | 12 | if ( 13 | isAdminRoute(req) && 14 | (await auth()).sessionClaims?.metadata?.role !== "admin" 15 | ) { 16 | const url = new URL("/", req.url); 17 | return NextResponse.redirect(url); 18 | } 19 | 20 | if (!userId && !isPublicRoute(req)) { 21 | // Add custom logic to run before redirecting 22 | 23 | return redirectToSignIn(); 24 | } 25 | }); 26 | 27 | export const config = { 28 | matcher: [ 29 | // Skip Next.js internals and all static files, unless found in search params 30 | "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", 31 | // Always run for API routes 32 | "/(api|trpc)(.*)", 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /authentication-demo/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /authentication-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /authentication-demo/types/globals.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | // Create a type for the roles 4 | export type Roles = "admin" | "moderator"; 5 | 6 | declare global { 7 | interface CustomJwtSessionClaims { 8 | metadata: { 9 | role?: Roles; 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /caching-revalidation-demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /caching-revalidation-demo/.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.* 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 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /caching-revalidation-demo/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /caching-revalidation-demo/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "id": 1, 5 | "title": "Product 1", 6 | "price": 800, 7 | "description": "Updated Description 1" 8 | }, 9 | { 10 | "id": 2, 11 | "title": "Product 2", 12 | "price": 1000, 13 | "description": "Description 2" 14 | }, 15 | { 16 | "id": 3, 17 | "title": "Product 3", 18 | "price": 2500, 19 | "description": "Description 3" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /caching-revalidation-demo/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /caching-revalidation-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caching-revalidation-demo", 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 | "serve-json": "json-server --watch db.json --port 3001" 11 | }, 12 | "dependencies": { 13 | "@prisma/client": "^5.22.0", 14 | "@types/json-server": "^0.14.7", 15 | "json-server": "^0.17.4", 16 | "next": "15.0.3", 17 | "react": "19.0.0-rc-66855b96-20241106", 18 | "react-dom": "19.0.0-rc-66855b96-20241106" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "eslint": "^8", 25 | "eslint-config-next": "15.0.3", 26 | "postcss": "^8", 27 | "prisma": "^5.22.0", 28 | "tailwindcss": "^3.4.1", 29 | "typescript": "^5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /caching-revalidation-demo/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /caching-revalidation-demo/prisma/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/caching-revalidation-demo/prisma/app.db -------------------------------------------------------------------------------- /caching-revalidation-demo/prisma/migrations/20241125081652_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Product" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "title" TEXT NOT NULL, 5 | "price" INTEGER NOT NULL, 6 | "description" TEXT 7 | ); 8 | -------------------------------------------------------------------------------- /caching-revalidation-demo/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /caching-revalidation-demo/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = "file:app.db" 11 | } 12 | 13 | model Product { 14 | id Int @id @default(autoincrement()) 15 | title String 16 | price Int 17 | description String? 18 | } -------------------------------------------------------------------------------- /caching-revalidation-demo/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /caching-revalidation-demo/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /caching-revalidation-demo/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /caching-revalidation-demo/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /caching-revalidation-demo/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/caching-revalidation-demo/src/app/favicon.ico -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/caching-revalidation-demo/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/caching-revalidation-demo/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/json-server-products/page.tsx: -------------------------------------------------------------------------------- 1 | type Product = { 2 | id: number; 3 | title: string; 4 | price: number; 5 | description: string | null; 6 | }; 7 | 8 | export default async function JSONServerProductsPage() { 9 | const response = await fetch("http://localhost:3001/products", { 10 | cache: "force-cache", 11 | }); 12 | const products: Product[] = await response.json(); 13 | return ( 14 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | 5 | const geistSans = localFont({ 6 | src: "./fonts/GeistVF.woff", 7 | variable: "--font-geist-sans", 8 | weight: "100 900", 9 | }); 10 | const geistMono = localFont({ 11 | src: "./fonts/GeistMonoVF.woff", 12 | variable: "--font-geist-mono", 13 | weight: "100 900", 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Create Next App", 18 | description: "Generated by create next app", 19 | }; 20 | 21 | export default function RootLayout({ 22 | children, 23 | }: Readonly<{ 24 | children: React.ReactNode; 25 | }>) { 26 | return ( 27 | 28 | 31 | {children} 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | Next.js logo 15 |
    16 |
  1. 17 | Get started by editing{" "} 18 | 19 | src/app/page.tsx 20 | 21 | . 22 |
  2. 23 |
  3. Save and see your changes instantly.
  4. 24 |
25 | 26 |
27 | 33 | Vercel logomark 40 | Deploy now 41 | 42 | 48 | Read our docs 49 | 50 |
51 |
52 | 99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/app/prisma-products/page.tsx: -------------------------------------------------------------------------------- 1 | import { getProducts } from "@/prisma-db"; 2 | import { unstable_cache } from "next/cache"; 3 | 4 | type Product = { 5 | id: number; 6 | title: string; 7 | price: number; 8 | description: string | null; 9 | }; 10 | 11 | const getProductsWithCache = unstable_cache(getProducts); 12 | 13 | export default async function PrismaProductsPage() { 14 | const products: Product[] = await getProductsWithCache(); 15 | return ( 16 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /caching-revalidation-demo/src/prisma-db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | const prisma = new PrismaClient(); 3 | 4 | const seedProducts = async () => { 5 | const count = await prisma.product.count(); 6 | if (count === 0) { 7 | await prisma.product.createMany({ 8 | data: [ 9 | { title: "Product 1", price: 500, description: "Description 1" }, 10 | { title: "Product 2", price: 700, description: "Description 2" }, 11 | { title: "Product 3", price: 1000, description: "Description 3" }, 12 | ], 13 | }); 14 | } 15 | }; 16 | 17 | // Run seed if needed 18 | seedProducts(); 19 | 20 | export async function getProducts() { 21 | await new Promise((resolve) => setTimeout(resolve, 1500)); 22 | return prisma.product.findMany(); 23 | } 24 | 25 | export async function getProduct(id: number) { 26 | await new Promise((resolve) => setTimeout(resolve, 1500)); 27 | return prisma.product.findUnique({ 28 | where: { id }, 29 | }); 30 | } 31 | 32 | export async function addProduct( 33 | title: string, 34 | price: number, 35 | description: string 36 | ) { 37 | await new Promise((resolve) => setTimeout(resolve, 1500)); 38 | return prisma.product.create({ 39 | data: { title, price, description }, 40 | }); 41 | } 42 | 43 | export async function updateProduct( 44 | id: number, 45 | title: string, 46 | price: number, 47 | description: string 48 | ) { 49 | await new Promise((resolve) => setTimeout(resolve, 1500)); 50 | return prisma.product.update({ 51 | where: { id }, 52 | data: { title, price, description }, 53 | }); 54 | } 55 | 56 | export async function deleteProduct(id: number) { 57 | await new Promise((resolve) => setTimeout(resolve, 1500)); 58 | return prisma.product.delete({ 59 | where: { id }, 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /caching-revalidation-demo/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /caching-revalidation-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /data-fetching-demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /data-fetching-demo/.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.* 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 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | 42 | # database 43 | /prisma/app.db 44 | -------------------------------------------------------------------------------- /data-fetching-demo/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /data-fetching-demo/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /data-fetching-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-fetching-demo", 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 | "@prisma/client": "^5.22.0", 13 | "next": "15.0.3", 14 | "react": "19.0.0-rc-66855b96-20241106", 15 | "react-dom": "19.0.0-rc-66855b96-20241106" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "15.0.3", 23 | "postcss": "^8", 24 | "prisma": "^5.22.0", 25 | "tailwindcss": "^3.4.1", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data-fetching-demo/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /data-fetching-demo/prisma/migrations/20241119043535_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Product" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "title" TEXT NOT NULL, 5 | "price" INTEGER NOT NULL, 6 | "description" TEXT 7 | ); 8 | -------------------------------------------------------------------------------- /data-fetching-demo/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /data-fetching-demo/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = "file:app.db" 11 | } 12 | 13 | model Product { 14 | id Int @id @default(autoincrement()) 15 | title String 16 | price Int 17 | description String? 18 | } -------------------------------------------------------------------------------- /data-fetching-demo/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-fetching-demo/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-fetching-demo/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-fetching-demo/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-fetching-demo/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-fetching-demo/src/actions/products.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { addProduct, updateProduct, deleteProduct } from "@/prisma-db"; 4 | import { redirect } from "next/navigation"; 5 | import { revalidatePath } from "next/cache"; 6 | 7 | export type Errors = { 8 | title?: string; 9 | price?: string; 10 | description?: string; 11 | }; 12 | 13 | export type FormState = { 14 | errors: Errors; 15 | }; 16 | 17 | export async function createProduct(prevState: FormState, formData: FormData) { 18 | const title = formData.get("title") as string; 19 | const price = formData.get("price") as string; 20 | const description = formData.get("description") as string; 21 | 22 | const errors: Errors = {}; 23 | 24 | if (!title) { 25 | errors.title = "Title is required"; 26 | } 27 | 28 | if (!price) { 29 | errors.price = "Price is required"; 30 | } 31 | 32 | if (!description) { 33 | errors.description = "Description is required"; 34 | } 35 | 36 | if (Object.keys(errors).length > 0) { 37 | return { errors }; 38 | } 39 | 40 | await addProduct(title, parseInt(price), description); 41 | redirect("/products-db"); 42 | } 43 | 44 | export async function editProduct( 45 | id: number, 46 | prevState: FormState, 47 | formData: FormData 48 | ) { 49 | const title = formData.get("title") as string; 50 | const price = formData.get("price") as string; 51 | const description = formData.get("description") as string; 52 | 53 | const errors: Errors = {}; 54 | 55 | if (!title) { 56 | errors.title = "Title is required"; 57 | } 58 | 59 | if (!price) { 60 | errors.price = "Price is required"; 61 | } 62 | 63 | if (!description) { 64 | errors.description = "Description is required"; 65 | } 66 | 67 | if (Object.keys(errors).length > 0) { 68 | return { errors }; 69 | } 70 | 71 | await updateProduct(id, title, parseInt(price), description); 72 | redirect("/products-db"); 73 | } 74 | 75 | export async function removeProduct(id: number) { 76 | await deleteProduct(id); 77 | revalidatePath("/products-db"); 78 | } 79 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/data-fetching-demo/src/app/favicon.ico -------------------------------------------------------------------------------- /data-fetching-demo/src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/data-fetching-demo/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /data-fetching-demo/src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/Next.js-15-Tutorials/908ec36a3607355c97f2f7173e2f4e5634a1e702/data-fetching-demo/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /data-fetching-demo/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | 5 | const geistSans = localFont({ 6 | src: "./fonts/GeistVF.woff", 7 | variable: "--font-geist-sans", 8 | weight: "100 900", 9 | }); 10 | const geistMono = localFont({ 11 | src: "./fonts/GeistMonoVF.woff", 12 | variable: "--font-geist-mono", 13 | weight: "100 900", 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Create Next App", 18 | description: "Generated by create next app", 19 | }; 20 | 21 | export default function RootLayout({ 22 | children, 23 | }: Readonly<{ 24 | children: React.ReactNode; 25 | }>) { 26 | return ( 27 | 28 | 31 | {children} 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Search from "@/components/search"; 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | 8 | Next.js logo 16 |
    17 |
  1. 18 | Get started by editing{" "} 19 | 20 | src/app/page.tsx 21 | 22 | . 23 |
  2. 24 |
  3. Save and see your changes instantly.
  4. 25 |
26 | 27 |
28 | 34 | Vercel logomark 41 | Deploy now 42 | 43 | 49 | Read our docs 50 | 51 |
52 |
53 | 100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/posts-sequential/author.tsx: -------------------------------------------------------------------------------- 1 | type User = { 2 | id: number; 3 | name: string; 4 | username: string; 5 | email: string; 6 | }; 7 | 8 | export async function Author({ userId }: { userId: number }) { 9 | await new Promise((resolve) => setTimeout(resolve, 1000)); 10 | const response = await fetch( 11 | `https://jsonplaceholder.typicode.com/users/${userId}` 12 | ); 13 | const user: User = await response.json(); 14 | 15 | return ( 16 |
17 | Written by:{" "} 18 | 19 | {user.name} 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/posts-sequential/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { Author } from "./author"; 3 | 4 | type Post = { 5 | userId: number; 6 | id: number; 7 | title: string; 8 | body: string; 9 | }; 10 | 11 | export default async function PostsPage() { 12 | const response = await fetch("https://jsonplaceholder.typicode.com/posts"); 13 | const posts: Post[] = await response.json(); 14 | 15 | const filteredPosts = posts.filter((post) => post.id % 10 === 1); 16 | 17 | return ( 18 |
19 |

Blog Posts

20 |
21 | {filteredPosts.map((post) => ( 22 |
23 |

24 | {post.title} 25 |

26 |

{post.body}

27 | Loading author...
30 | } 31 | > 32 | 33 | 34 |
35 | ))} 36 |
37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /data-fetching-demo/src/app/products-db-create/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FormState, createProduct } from "@/actions/products"; 4 | import { Submit } from "@/components/submit"; 5 | import { useActionState } from "react"; 6 | 7 | export default function AddProductPage() { 8 | const initialState: FormState = { 9 | errors: {}, 10 | }; 11 | 12 | const [state, formAction] = useActionState(createProduct, initialState); 13 | 14 | return ( 15 |
16 |
17 | 25 | {state.errors.title && ( 26 |

{state.errors.title}

27 | )} 28 |
29 |
30 | 38 | {state.errors.price && ( 39 |

{state.errors.price}

40 | )} 41 |
42 |
43 |