├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── public ├── 1.json ├── 2.json ├── 3.json ├── next.svg ├── thirteen.svg └── vercel.svg ├── src ├── app │ ├── api │ │ └── hello │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.module.css │ └── page.tsx ├── components │ ├── FirstName.tsx │ └── LastName.tsx └── lib │ ├── UserIdContext.tsx │ └── fetchUser.ts └── tsconfig.json /.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 a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/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 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | 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. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | -------------------------------------------------------------------------------- /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": "nextjs-react-cache", 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 | "@types/node": "18.15.3", 13 | "@types/react": "18.0.28", 14 | "@types/react-dom": "18.0.11", 15 | "eslint": "8.36.0", 16 | "eslint-config-next": "13.2.4", 17 | "next": "13.2.4", 18 | "react": "18.2.0", 19 | "react-dom": "18.2.0", 20 | "typescript": "5.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1", 3 | "first": "Sally", 4 | "last": "Jones", 5 | "job": "Software Developer", 6 | "email": "sjones@nowhere.com" 7 | } 8 | -------------------------------------------------------------------------------- /public/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2", 3 | "first": "Jamie", 4 | "last": "Lee", 5 | "job": "QA Engineer", 6 | "email": "jlee@nowhere.com" 7 | } 8 | -------------------------------------------------------------------------------- /public/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2", 3 | "first": "Sally", 4 | "last": "Sue", 5 | "job": "Account Specialist", 6 | "email": "ssue@nowehere.com" 7 | } 8 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, Next.js!') 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/nextjs-react-cache/00ce0fe5537d3420a6e0f02898597012b2f03d07/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | background: black; 3 | color: white; 4 | font-size: xx-large; 5 | padding: 1rem; 6 | font-family: Arial, Helvetica, sans-serif; 7 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | 3 | export const metadata = { 4 | title: 'Create Next App', 5 | description: 'Generated by create next app', 6 | } 7 | 8 | export default function RootLayout({ 9 | children, 10 | }: { 11 | children: React.ReactNode 12 | }) { 13 | return ( 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/app/page.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 6rem; 7 | min-height: 100vh; 8 | } 9 | 10 | .description { 11 | display: inherit; 12 | justify-content: inherit; 13 | align-items: inherit; 14 | font-size: 0.85rem; 15 | max-width: var(--max-width); 16 | width: 100%; 17 | z-index: 2; 18 | font-family: var(--font-mono); 19 | } 20 | 21 | .description a { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | gap: 0.5rem; 26 | } 27 | 28 | .description p { 29 | position: relative; 30 | margin: 0; 31 | padding: 1rem; 32 | background-color: rgba(var(--callout-rgb), 0.5); 33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3); 34 | border-radius: var(--border-radius); 35 | } 36 | 37 | .code { 38 | font-weight: 700; 39 | font-family: var(--font-mono); 40 | } 41 | 42 | .grid { 43 | display: grid; 44 | grid-template-columns: repeat(3, minmax(33%, auto)); 45 | width: var(--max-width); 46 | max-width: 100%; 47 | } 48 | 49 | .card { 50 | padding: 1rem 1.2rem; 51 | border-radius: var(--border-radius); 52 | background: rgba(var(--card-rgb), 0); 53 | border: 1px solid rgba(var(--card-border-rgb), 0); 54 | transition: background 200ms, border 200ms; 55 | } 56 | 57 | .card span { 58 | display: inline-block; 59 | transition: transform 200ms; 60 | } 61 | 62 | .card h2 { 63 | font-weight: 600; 64 | margin-bottom: 0.7rem; 65 | } 66 | 67 | .card p { 68 | margin: 0; 69 | opacity: 0.6; 70 | font-size: 0.9rem; 71 | line-height: 1.5; 72 | max-width: 34ch; 73 | } 74 | 75 | .center { 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | position: relative; 80 | padding: 4rem 0; 81 | } 82 | 83 | .center::before { 84 | background: var(--secondary-glow); 85 | border-radius: 50%; 86 | width: 480px; 87 | height: 360px; 88 | margin-left: -400px; 89 | } 90 | 91 | .center::after { 92 | background: var(--primary-glow); 93 | width: 240px; 94 | height: 180px; 95 | z-index: -1; 96 | } 97 | 98 | .center::before, 99 | .center::after { 100 | content: ''; 101 | left: 50%; 102 | position: absolute; 103 | filter: blur(45px); 104 | transform: translateZ(0); 105 | } 106 | 107 | .logo, 108 | .thirteen { 109 | position: relative; 110 | } 111 | 112 | .thirteen { 113 | display: flex; 114 | justify-content: center; 115 | align-items: center; 116 | width: 75px; 117 | height: 75px; 118 | padding: 25px 10px; 119 | margin-left: 16px; 120 | transform: translateZ(0); 121 | border-radius: var(--border-radius); 122 | overflow: hidden; 123 | box-shadow: 0px 2px 8px -1px #0000001a; 124 | } 125 | 126 | .thirteen::before, 127 | .thirteen::after { 128 | content: ''; 129 | position: absolute; 130 | z-index: -1; 131 | } 132 | 133 | /* Conic Gradient Animation */ 134 | .thirteen::before { 135 | animation: 6s rotate linear infinite; 136 | width: 200%; 137 | height: 200%; 138 | background: var(--tile-border); 139 | } 140 | 141 | /* Inner Square */ 142 | .thirteen::after { 143 | inset: 0; 144 | padding: 1px; 145 | border-radius: var(--border-radius); 146 | background: linear-gradient( 147 | to bottom right, 148 | rgba(var(--tile-start-rgb), 1), 149 | rgba(var(--tile-end-rgb), 1) 150 | ); 151 | background-clip: content-box; 152 | } 153 | 154 | /* Enable hover only on non-touch devices */ 155 | @media (hover: hover) and (pointer: fine) { 156 | .card:hover { 157 | background: rgba(var(--card-rgb), 0.1); 158 | border: 1px solid rgba(var(--card-border-rgb), 0.15); 159 | } 160 | 161 | .card:hover span { 162 | transform: translateX(4px); 163 | } 164 | } 165 | 166 | @media (prefers-reduced-motion) { 167 | .thirteen::before { 168 | animation: none; 169 | } 170 | 171 | .card:hover span { 172 | transform: none; 173 | } 174 | } 175 | 176 | /* Mobile and Tablet */ 177 | @media (max-width: 1023px) { 178 | .content { 179 | padding: 4rem; 180 | } 181 | 182 | .grid { 183 | grid-template-columns: 1fr; 184 | margin-bottom: 120px; 185 | max-width: 320px; 186 | text-align: center; 187 | } 188 | 189 | .card { 190 | padding: 1rem 2.5rem; 191 | } 192 | 193 | .card h2 { 194 | margin-bottom: 0.5rem; 195 | } 196 | 197 | .center { 198 | padding: 8rem 0 6rem; 199 | } 200 | 201 | .center::before { 202 | transform: none; 203 | height: 300px; 204 | } 205 | 206 | .description { 207 | font-size: 0.8rem; 208 | } 209 | 210 | .description a { 211 | padding: 1rem; 212 | } 213 | 214 | .description p, 215 | .description div { 216 | display: flex; 217 | justify-content: center; 218 | position: fixed; 219 | width: 100%; 220 | } 221 | 222 | .description p { 223 | align-items: center; 224 | inset: 0 0 auto; 225 | padding: 2rem 1rem 1.4rem; 226 | border-radius: 0; 227 | border: none; 228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); 229 | background: linear-gradient( 230 | to bottom, 231 | rgba(var(--background-start-rgb), 1), 232 | rgba(var(--callout-rgb), 0.5) 233 | ); 234 | background-clip: padding-box; 235 | backdrop-filter: blur(24px); 236 | } 237 | 238 | .description div { 239 | align-items: flex-end; 240 | pointer-events: none; 241 | inset: auto 0 0; 242 | padding: 2rem; 243 | height: 200px; 244 | background: linear-gradient( 245 | to bottom, 246 | transparent 0%, 247 | rgb(var(--background-end-rgb)) 40% 248 | ); 249 | z-index: 1; 250 | } 251 | } 252 | 253 | @media (prefers-color-scheme: dark) { 254 | .vercelLogo { 255 | filter: invert(1); 256 | } 257 | 258 | .logo, 259 | .thirteen img { 260 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 261 | } 262 | } 263 | 264 | @keyframes rotate { 265 | from { 266 | transform: rotate(360deg); 267 | } 268 | to { 269 | transform: rotate(0deg); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import FirstName from "@/components/FirstName"; 4 | import LastName from "@/components/LastName"; 5 | import { UserIdProvider } from "@/lib/UserIdContext"; 6 | 7 | export default function Home() { 8 | const [userId, setUserId] = useState(1); 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/FirstName.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { use } from "react"; 3 | import { fetchUser } from "@/lib/fetchUser"; 4 | import { useUserId } from "@/lib/UserIdContext"; 5 | 6 | function FirstName() { 7 | const id = useUserId(); 8 | if (!id) { 9 | return null; 10 | } 11 | const data = use(fetchUser(id)); 12 | return
First: {data.first}
; 13 | } 14 | 15 | export default FirstName; 16 | -------------------------------------------------------------------------------- /src/components/LastName.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { use } from "react"; 3 | import { fetchUser } from "@/lib/fetchUser"; 4 | import { useUserId } from "@/lib/UserIdContext"; 5 | 6 | function LastName() { 7 | const id = useUserId(); 8 | if (!id) { 9 | return null; 10 | } 11 | const data = use(fetchUser(id)); 12 | return
Last: {data.last}
; 13 | } 14 | 15 | export default LastName; 16 | -------------------------------------------------------------------------------- /src/lib/UserIdContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | 3 | const UserIdContext = createContext(null); 4 | 5 | export const UserIdProvider = ({ 6 | userId, 7 | children, 8 | }: { 9 | userId: number; 10 | children: React.ReactNode; 11 | }) => { 12 | return ( 13 | {children} 14 | ); 15 | }; 16 | 17 | export const useUserId = () => useContext(UserIdContext); 18 | -------------------------------------------------------------------------------- /src/lib/fetchUser.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "react"; 2 | 3 | export const fetchUser = cache((id: number) => 4 | fetch(`http://localhost:3000/${id}.json?q=1`).then((res) => res.json()) 5 | ); 6 | -------------------------------------------------------------------------------- /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 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------