-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fe02aee
Showing
25 changed files
with
862 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Next.js Redis Cluster Cache Integration Example | ||
|
||
Run docker compose to start redis locally | ||
|
||
``` | ||
docker-compose up -d | ||
``` | ||
|
||
open redis-1 terminal and create the cluster | ||
|
||
``` | ||
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1 | ||
``` | ||
|
||
type "yes" to apply the configuration. | ||
|
||
verify that the cluster was created | ||
|
||
``` | ||
redis-cli -c | ||
cluster nodes | ||
``` | ||
|
||
install next.js dependencies for the project | ||
|
||
``` | ||
npm i | ||
``` | ||
|
||
build next.js (will not use custom redis cache handler during build) | ||
|
||
``` | ||
npm run build | ||
``` | ||
|
||
start next.js app | ||
|
||
``` | ||
npm run start | ||
``` | ||
|
||
navigate to the local homepage [http://localhost:3000/cet](http://localhost:3000/cet) | ||
|
||
to remove logs, remove NEXT_PRIVATE_DEBUG_CACHE=1 from package.json | ||
|
||
keep in mind that redis data will be stored in redis/node-X/data | ||
|
||
to flush all the redis cluster use | ||
|
||
``` | ||
redis-cli --cluster call --cluster-only-masters 172.38.0.11:6379 FLUSHALL | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { notFound } from "next/navigation"; | ||
import { CacheStateWatcher } from "../cache-state-watcher"; | ||
import { Suspense } from "react"; | ||
import { RevalidateFrom } from "../revalidate-from"; | ||
import Link from "next/link"; | ||
|
||
type TimeData = { | ||
unixtime: number; | ||
datetime: string; | ||
timezone: string; | ||
}; | ||
|
||
const timeZones = ["cet", "gmt"]; | ||
|
||
export const revalidate = 500; | ||
|
||
export async function generateStaticParams() { | ||
return timeZones.map((timezone) => ({ timezone })); | ||
} | ||
|
||
export default async function Page({ params: { timezone } }) { | ||
const data = await fetch( | ||
`https://worldtimeapi.org/api/timezone/${timezone}`, | ||
{ | ||
next: { tags: ["time-data"] }, | ||
}, | ||
); | ||
|
||
if (!data.ok) { | ||
notFound(); | ||
} | ||
|
||
const timeData: TimeData = await data.json(); | ||
|
||
return ( | ||
<> | ||
<header className="header"> | ||
{timeZones.map((timeZone) => ( | ||
<Link key={timeZone} className="link" href={`/${timeZone}`}> | ||
{timeZone.toUpperCase()} Time | ||
</Link> | ||
))} | ||
</header> | ||
<main className="widget"> | ||
<div className="pre-rendered-at"> | ||
{timeData.timezone} Time {timeData.datetime} | ||
</div> | ||
<Suspense fallback={null}> | ||
<CacheStateWatcher | ||
revalidateAfter={revalidate * 1000} | ||
time={timeData.unixtime * 1000} | ||
/> | ||
</Suspense> | ||
<RevalidateFrom /> | ||
</main> | ||
<footer className="footer"> | ||
<Link | ||
href={process.env.NEXT_PUBLIC_REDIS_INSIGHT_URL} | ||
className="link" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
View RedisInsight ↪ | ||
</Link> | ||
</footer> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
"use client"; | ||
|
||
import { ReactNode, useEffect, useState } from "react"; | ||
|
||
type CacheStateWatcherProps = { time: number; revalidateAfter: number }; | ||
|
||
export function CacheStateWatcher({ | ||
time, | ||
revalidateAfter, | ||
}: CacheStateWatcherProps): ReactNode { | ||
const [cacheState, setCacheState] = useState(""); | ||
const [countDown, setCountDown] = useState(""); | ||
|
||
useEffect(() => { | ||
let id = -1; | ||
|
||
function check(): void { | ||
const now = Date.now(); | ||
|
||
setCountDown( | ||
Math.max(0, (time + revalidateAfter - now) / 1000).toFixed(3), | ||
); | ||
|
||
if (now > time + revalidateAfter) { | ||
setCacheState("stale"); | ||
|
||
return; | ||
} | ||
|
||
setCacheState("fresh"); | ||
|
||
id = requestAnimationFrame(check); | ||
} | ||
|
||
id = requestAnimationFrame(check); | ||
|
||
return () => { | ||
cancelAnimationFrame(id); | ||
}; | ||
}, [revalidateAfter, time]); | ||
|
||
return ( | ||
<> | ||
<div className={`cache-state ${cacheState}`}> | ||
Cache state: {cacheState} | ||
</div> | ||
<div className="stale-after">Stale in: {countDown}</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
*, | ||
*:before, | ||
*:after { | ||
box-sizing: border-box; | ||
} | ||
|
||
body, | ||
html { | ||
margin: 0; | ||
padding: 0; | ||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; | ||
color: #333; | ||
line-height: 1.6; | ||
background-color: #f4f4f4; | ||
} | ||
|
||
.widget { | ||
background-color: #ffffff; | ||
border-radius: 8px; | ||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||
margin: 20px auto; | ||
padding: 20px; | ||
max-width: 600px; | ||
text-align: center; | ||
} | ||
|
||
.pre-rendered-at, | ||
.cache-state, | ||
.stale-after { | ||
font-size: 0.9em; | ||
color: #666; | ||
margin: 5px 0; | ||
} | ||
|
||
.cache-state.fresh { | ||
color: #4caf50; | ||
} | ||
|
||
.cache-state.stale { | ||
color: #f44336; | ||
} | ||
|
||
.revalidate-from { | ||
margin-top: 20px; | ||
} | ||
|
||
.revalidate-from-button { | ||
background-color: #008cba; | ||
color: white; | ||
border: none; | ||
border-radius: 4px; | ||
padding: 10px 20px; | ||
cursor: pointer; | ||
transition: background-color 0.3s ease; | ||
} | ||
|
||
.revalidate-from-button:hover { | ||
background-color: #005f73; | ||
} | ||
|
||
.revalidate-from-button:active { | ||
transform: translateY(2px); | ||
} | ||
|
||
.revalidate-from-button[aria-disabled="true"] { | ||
background-color: #ccc; | ||
cursor: not-allowed; | ||
} | ||
|
||
.footer, | ||
.header { | ||
padding: 10px; | ||
position: relative; | ||
place-items: center; | ||
grid-auto-flow: column; | ||
bottom: 0; | ||
grid-gap: 20px; | ||
width: 100%; | ||
display: grid; | ||
justify-content: center; | ||
} | ||
|
||
.link { | ||
color: #09f; | ||
text-decoration: none; | ||
transition: color 0.3s ease; | ||
} | ||
|
||
.link:hover { | ||
color: #07c; | ||
} | ||
|
||
@media (max-width: 768px) { | ||
.widget { | ||
width: 90%; | ||
margin: 20px auto; | ||
} | ||
|
||
.footer { | ||
padding: 20px; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import "./global.css"; | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return ( | ||
<html lang="en"> | ||
<body>{children}</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"use client"; | ||
|
||
import { useFormStatus } from "react-dom"; | ||
import revalidate from "./server-actions"; | ||
|
||
function RevalidateButton() { | ||
const { pending } = useFormStatus(); | ||
|
||
return ( | ||
<button | ||
className="revalidate-from-button" | ||
type="submit" | ||
disabled={pending} | ||
aria-disabled={pending} | ||
> | ||
Revalidate | ||
</button> | ||
); | ||
} | ||
|
||
export function RevalidateFrom() { | ||
return ( | ||
<form className="revalidate-from" action={revalidate}> | ||
<RevalidateButton /> | ||
</form> | ||
); | ||
} |
Oops, something went wrong.