Skip to content

Commit e0e5bc7

Browse files
committed
Next batch of features
1 parent 36e31da commit e0e5bc7

File tree

26 files changed

+516
-10
lines changed

26 files changed

+516
-10
lines changed

Diff for: next.config.mjs

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ const nextConfig = {
77
hostname: 'www.nyc.gov',
88
},
99
]
10+
},
11+
redirects: async () => {
12+
return [
13+
{
14+
source: "/next-config-redirect-missing",
15+
destination: "/config-redirect?missing=true",
16+
permanent: true,
17+
missing: [{ type: "cookie", key: "missing-cookie" }],
18+
}
19+
];
1020
}
1121
};
1222

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"dev": "next dev",
77
"build": "next build",
88
"start": "next start",
9+
"prod": "next build && next start",
910
"lint": "next lint"
1011
},
1112
"dependencies": {

Diff for: src/app/actions.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use server";
2+
3+
const data = {
4+
songs: [
5+
{
6+
"rank": 1,
7+
"title": "I'm never gonna give you up",
8+
"artist": "Rick Astley",
9+
"album": "Hold Me In Your Arms",
10+
"year": "1965",
11+
"videoId": "dQw4w9WgXcQ"
12+
},
13+
{
14+
"rank": 2,
15+
"title": "My Wang",
16+
"artist": "Frank Wangnatra",
17+
"album": "@franjiewang",
18+
"year": "2023",
19+
"videoId": "qQzdAsjWGPg"
20+
},
21+
{
22+
"rank": 3,
23+
"title": "Excuse me miSST",
24+
"artist": "Jay-Air",
25+
"album": "@Jayair",
26+
"year": "2023",
27+
"videoId": "tnDh0JhmaFw"
28+
},
29+
{
30+
"rank": 4,
31+
"title": "I don't want another CONSOLE-RRY",
32+
"artist": "Dax",
33+
"album": "@thxdr",
34+
"year": "2023",
35+
"videoId": "4JI70_9acgE"
36+
}
37+
]
38+
}
39+
40+
export type Song = (typeof data.songs)[0];
41+
export type Album = { album: string; artist: string; songs: Song[] };
42+
const albumsMap: { [key: string]: Song[] } = {};
43+
44+
const albums: Album[] = [];
45+
data.songs.forEach((s) => {
46+
if (!albumsMap[s.album]) {
47+
albumsMap[s.album] = [s];
48+
} else {
49+
albumsMap[s.album].push(s);
50+
}
51+
});
52+
53+
Object.entries(albumsMap).forEach(([key, album]) => {
54+
albums.push({
55+
album: album[0].album,
56+
artist: album[0].artist,
57+
songs: album,
58+
});
59+
});
60+
61+
export async function getAlbums() {
62+
return albums;
63+
}
64+
65+
export async function getSongs() {
66+
return data.songs;
67+
}
68+
69+
export async function getSong(album: string, title: string) {
70+
return data.songs.find(
71+
(song) =>
72+
song.album === decodeURIComponent(album) &&
73+
song.title === decodeURIComponent(title),
74+
);
75+
}

Diff for: src/app/api/client/route.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function GET(request: Request) {
4+
return NextResponse.json({
5+
hello: "client",
6+
});
7+
}

Diff for: src/app/api/host/route.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function GET(request: Request) {
4+
return NextResponse.json({
5+
url: request.url,
6+
});
7+
}

Diff for: src/app/api/isr/route.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import fs from "fs/promises";
2+
import { NextRequest, NextResponse } from "next/server";
3+
import path from "path";
4+
5+
export const dynamic = "force-dynamic";
6+
7+
8+
export async function GET(request: NextRequest) {
9+
const cwd = process.cwd();
10+
// const prerenderManifest = await fs.readFile(
11+
// path.join(cwd, ".next/prerender-manifest.json"),
12+
// "utf-8",
13+
// );
14+
// const manifest = JSON.parse(prerenderManifest);
15+
// const previewId = manifest.preview.previewModeId;
16+
17+
18+
// x-prerender-revalidate is a custom header that tells the prerendering service to revalidate the page after the specified number of seconds
19+
console.log(`https://${request.headers.get("host")}/render/isr`)
20+
const result = await fetch(`http://${request.headers.get("host")}/render/isr`, {
21+
headers: { "x-prerender-revalidate": "10" },
22+
method: "HEAD",
23+
});
24+
25+
return NextResponse.json({
26+
status: 200,
27+
body: {
28+
result: result.ok,
29+
cacheControl: result.headers.get("cache-control"),
30+
},
31+
});
32+
}

Diff for: src/app/api/revalidate-tag/route.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { revalidateTag } from "next/cache";
2+
3+
export async function GET() {
4+
revalidateTag("revalidate");
5+
6+
return new Response("ok");
7+
}

Diff for: src/app/api/sse/route.tsx

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { NextRequest } from "next/server";
2+
3+
export const dynamic = "force-dynamic";
4+
5+
async function wait(n: number = 1000) {
6+
return new Promise((res) => {
7+
setTimeout(res, n);
8+
});
9+
}
10+
11+
export async function GET(request: NextRequest) {
12+
const resStream = new TransformStream();
13+
const writer = resStream.writable.getWriter();
14+
15+
const res = new Response(resStream.readable, {
16+
headers: {
17+
"Content-Type": "text/event-stream",
18+
Connection: "keep-alive",
19+
"Cache-Control": "no-cache, no-transform",
20+
},
21+
});
22+
23+
setTimeout(async () => {
24+
writer.write(
25+
`data: ${JSON.stringify({
26+
message: "open",
27+
time: new Date().toISOString(),
28+
})}\n\n`,
29+
);
30+
for (let i = 1; i <= 4; i++) {
31+
await wait(2000);
32+
writer.write(
33+
`data: ${JSON.stringify({
34+
message: "hello:" + i,
35+
time: new Date().toISOString(),
36+
})}\n\n`,
37+
);
38+
}
39+
40+
await wait(2000);
41+
writer.write(
42+
`data: ${JSON.stringify({
43+
message: "close",
44+
time: new Date().toISOString(),
45+
})}\n\n`,
46+
);
47+
await wait(5000);
48+
await writer.close();
49+
}, 100);
50+
51+
return res;
52+
}

Diff for: src/app/config/redirect-restination/page.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default async function ISR() {
2+
return (
3+
<>
4+
<h1>return new Date().toISOString();</h1>
5+
<div>SSG</div>
6+
</>
7+
)
8+
}

Diff for: src/app/config/redirect/page.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default async function ISR() {
2+
return (
3+
<>
4+
<h1>return new Date().toISOString();</h1>
5+
<div>SSG</div>
6+
</>
7+
)
8+
}

Diff for: src/app/data-fetching/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return null;
3+
}

Diff for: src/app/data-fetching/server-actions/client.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
import { useCallback, useState, useTransition } from "react";
3+
4+
import type { Song as SongType } from "../../actions";
5+
import { getSong } from "../../actions";
6+
7+
8+
export default function Client() {
9+
const [isPending, startTransition] = useTransition();
10+
const [song, setSong] = useState<SongType>();
11+
12+
const onClick = useCallback(() => {
13+
startTransition(async () => {
14+
const song = await getSong(
15+
"Hold Me In Your Arms",
16+
"I'm never gonna give you up",
17+
);
18+
setSong(song);
19+
});
20+
}, []);
21+
22+
return (
23+
<div>
24+
<button onClick={onClick}>Fire Server Actions</button>
25+
{isPending && <div>☎️ing Server Actions...</div>}
26+
{song?.artist}
27+
</div>
28+
);
29+
}

Diff for: src/app/data-fetching/server-actions/page.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Client from "./client";
2+
3+
export default function Page() {
4+
return (
5+
<div>
6+
<h1>Server Actions</h1>
7+
<Client />
8+
</div>
9+
);
10+
}

Diff for: src/app/data-fetching/sse/page.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
5+
export default function SSE() {
6+
const [events, setEvents] = useState<any[]>([]);
7+
8+
useEffect(() => {
9+
const e = new EventSource("/api/sse");
10+
11+
e.onmessage = (msg) => {
12+
console.log(msg);
13+
try {
14+
const data = JSON.parse(msg.data);
15+
if (data.message === "close") {
16+
e.close();
17+
console.log("closing");
18+
}
19+
setEvents((prev) => prev.concat(data));
20+
} catch (err) {
21+
console.log("failed to parse: ", err, msg);
22+
}
23+
};
24+
}, []);
25+
26+
return (
27+
<>
28+
<h1>Server Sent Event</h1>
29+
{events.map((e, i) => (
30+
<div key={i}>
31+
Message {i}: {JSON.stringify(e)}
32+
</div>
33+
))}
34+
</>
35+
);
36+
}

Diff for: src/app/middleware/cookie/page.tsx

+6-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
async function getTime() {
2-
return new Date().toISOString();
3-
}
4-
5-
export const revalidate = 10;
1+
import Image from "next/image";
62

7-
export default async function ISR() {
8-
const time = getTime();
9-
return <div>Time: {time}</div>;
10-
}
3+
export default function Redirect() {
4+
return (
5+
<h1>coockie</h1>
6+
);
7+
}

Diff for: src/app/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default function Home() {
6666
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
6767
>
6868
<h2 className="mb-3 text-2xl font-semibold">
69-
Cache
69+
Render
7070
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
7171
-&gt;
7272
</span>

Diff for: src/app/render/isr/page.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
async function getTime() {
2+
return new Date().toISOString();
3+
}
4+
5+
export const revalidate = 10;
6+
7+
export default async function ISR() {
8+
const time = getTime();
9+
return (
10+
<>
11+
<h1>export const revalidate = 10;</h1>
12+
<div>Time: {time}</div>
13+
</>
14+
)
15+
}

0 commit comments

Comments
 (0)