Skip to content

Commit dc9f122

Browse files
Prepare @neshca/cache-handler for API stabilization (caching-tools#93)
- Improve `@neshca/backend` responses - Improve test pages - Improve cache-testing build script - Add partial pre-rendering page - Update dependencies
1 parent 22d3b17 commit dc9f122

24 files changed

+529
-407
lines changed

apps/cache-testing/.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
REDIS_URL=redis://localhost:6379
2+
REMOTE_CACHE_SERVER_BASE_URL=http://localhost:8080
3+
HOSTNAME=localhost
4+
PPR_ENABLED=false

apps/cache-testing/create-instances.sh

+28-48
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,35 @@
22

33
# Set common paths
44
STANDALONE_DIR="$PWD/.next/standalone"
5-
PUBLIC_DIR="$PWD/public"
65
APP_DIR="$STANDALONE_DIR/apps/cache-testing"
6+
PUBLIC_DIR="$PWD/public"
77
STATIC_DIR="$PWD/.next/static"
88
INSTANCES_DIR="$PWD/.next/__instances"
99

10-
# Remove existing $INSTANCES_DIR directory
11-
if ! rm -rf $INSTANCES_DIR; then
12-
echo "Failed to remove existing $INSTANCES_DIR directory"
13-
exit 1
14-
fi
15-
16-
# Create $INSTANCES_DIR directory
17-
if ! mkdir -p $INSTANCES_DIR/3000; then
18-
echo "Failed to create $INSTANCES_DIR/3000 directory"
19-
exit 1
20-
fi
21-
22-
# Create $INSTANCES_DIR directory
23-
if ! mkdir -p $INSTANCES_DIR/3001; then
24-
echo "Failed to create $INSTANCES_DIR/3001 directory"
25-
exit 1
26-
fi
27-
28-
# Copy files from $STANDALONE_DIR to $INSTANCES_DIR/3000
29-
if ! cp -r $STANDALONE_DIR/. $INSTANCES_DIR/3000/; then
30-
echo "Failed to copy standalone directory"
31-
exit 1
32-
fi
33-
34-
# Copy $PUBLIC_DIR to $INSTANCES_DIR/3000/public
35-
if ! cp -r $PUBLIC_DIR/ $INSTANCES_DIR/3000/public/; then
36-
echo "Failed to copy public directory"
37-
exit 1
38-
fi
39-
40-
# Copy files from $APP_DIR to $INSTANCES_DIR/3000
41-
if ! cp -r $STANDALONE_DIR/apps/cache-testing/. $INSTANCES_DIR/3000/; then
42-
echo "Failed to copy app directory"
43-
exit 1
44-
fi
45-
46-
# Copy $STATIC_DIR to $INSTANCES_DIR/3000/.next/static
47-
if ! cp -r $STATIC_DIR/ $INSTANCES_DIR/3000/.next/static/; then
48-
echo "Failed to copy static directory"
49-
exit 1
50-
fi
51-
52-
# Copy files from $INSTANCES_DIR/3000 to $INSTANCES_DIR/3001
53-
if ! cp -r $INSTANCES_DIR/3000/. $INSTANCES_DIR/3001/.; then
54-
echo "Failed to copy $INSTANCES_DIR from 3000 to 3001"
55-
exit 1
56-
fi
10+
copy_dir() {
11+
if ! cp -r "$1" "$2"; then
12+
echo "Failed to copy from $1 to $2"
13+
exit 1
14+
fi
15+
}
16+
17+
# Copy public directory to standalone app directory
18+
copy_dir "$PUBLIC_DIR/" "$APP_DIR/public"
19+
20+
# Copy static directory to standalone app/.next directory
21+
copy_dir "$STATIC_DIR/" "$APP_DIR/.next/static"
22+
23+
create_instance_dir() {
24+
if ! mkdir -p "$INSTANCES_DIR/$1"; then
25+
echo "Failed to create $INSTANCES_DIR/$1 directory"
26+
exit 1
27+
fi
28+
}
29+
30+
# Create instance directories
31+
create_instance_dir 3000
32+
create_instance_dir 3001
33+
34+
# Copy files from standalone directory to instance directories
35+
copy_dir "$STANDALONE_DIR/." "$INSTANCES_DIR/3000"
36+
copy_dir "$STANDALONE_DIR/." "$INSTANCES_DIR/3001"

apps/cache-testing/next.config.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const path = require('node:path');
2+
13
const incrementalCacheHandlerPath = require.resolve(
24
process.env.CI ? './cache-handler-http' : './cache-handler-redis-stack',
35
);
@@ -8,8 +10,11 @@ const nextConfig = {
810
reactStrictMode: true,
911
output: 'standalone',
1012
experimental: {
11-
incrementalCacheHandlerPath,
13+
incrementalCacheHandlerPath: process.env.NODE_ENV !== 'development' ? incrementalCacheHandlerPath : undefined,
14+
// PPR should only be configured via the PPR_ENABLED env variable due to conditional logic in tests.
15+
ppr: process.env.PPR_ENABLED === 'true',
1216
largePageDataBytes: 1024 * 1024, // 1MB
17+
outputFileTracingRoot: path.join(__dirname, '../../'),
1318
},
1419
};
1520

apps/cache-testing/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
"e2e": "playwright test --config=./playwright.config.ts",
1414
"e2e:ui": "playwright test --ui --config=./playwright.config.ts",
1515
"lint": "next lint",
16-
"start": "REDIS_URL=redis://localhost:6379 SERVER_STARTED=1 node .next/__instances/3000/server.js"
16+
"start": "dotenv -e .env.local -v SERVER_STARTED=1 node .next/standalone/apps/cache-testing/server.js"
1717
},
1818
"dependencies": {
1919
"lru-cache": "10.0.1",
20-
"next": "14.0.1",
20+
"next": "14.0.2-canary.12",
2121
"react": "18.2.0",
2222
"react-dom": "18.2.0",
2323
"undici": "5.27.2"
@@ -26,11 +26,12 @@
2626
"@neshca/cache-handler": "*",
2727
"@neshca/json-replacer-reviver": "*",
2828
"@neshca/tsconfig": "*",
29-
"@next/eslint-plugin-next": "14.0.1",
29+
"@next/eslint-plugin-next": "14.0.2-canary.12",
3030
"@playwright/test": "1.39.0",
3131
"@types/node": "20.8.10",
3232
"@types/react": "18.2.36",
3333
"@types/react-dom": "18.2.14",
34+
"dotenv-cli": "7.3.0",
3435
"eslint-config-neshca": "*",
3536
"fastify": "4.24.3",
3637
"pm2": "5.3.0",

apps/cache-testing/src/app/api/revalidate-app/route.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import { revalidatePath, revalidateTag } from 'next/cache';
22
import type { NextRequest } from 'next/server';
33
import { NextResponse } from 'next/server';
4+
import { formatTime } from 'cache-testing/utils/format-time';
45

56
export function GET(request: NextRequest): Promise<NextResponse> {
67
const path = request.nextUrl.searchParams.get('path');
78
const tag = request.nextUrl.searchParams.get('tag');
89

9-
const now = new Date().toLocaleTimeString('ru-RU', {
10-
fractionalSecondDigits: 3,
11-
hour: '2-digit',
12-
minute: '2-digit',
13-
second: '2-digit',
14-
});
10+
const time = formatTime(Date.now(), 3);
1511

1612
if (path) {
1713
revalidatePath(path);
1814
return Promise.resolve(
1915
NextResponse.json({
2016
revalidated: true,
21-
now,
17+
now: time,
2218
}),
2319
);
2420
}
@@ -28,15 +24,15 @@ export function GET(request: NextRequest): Promise<NextResponse> {
2824
return Promise.resolve(
2925
NextResponse.json({
3026
revalidated: true,
31-
now,
27+
now: time,
3228
}),
3329
);
3430
}
3531

3632
return Promise.resolve(
3733
NextResponse.json({
3834
revalidated: false,
39-
now,
35+
now: time,
4036
message: 'Missing path to revalidate',
4137
}),
4238
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Suspense } from 'react';
2+
import { unstable_noStore as noStore } from 'next/cache';
3+
import { formatTime } from 'cache-testing/utils/format-time';
4+
import type { TimeBackendApiResponseJson } from 'cache-testing/utils/types';
5+
6+
async function ActualData(): Promise<JSX.Element> {
7+
noStore();
8+
9+
const response = await fetch('http://localhost:8081/time', {
10+
next: { tags: ['/app/ppr'] },
11+
});
12+
13+
if (!response.ok) {
14+
throw new Error('Failed to fetch unix time');
15+
}
16+
17+
const data = (await response.json()) as TimeBackendApiResponseJson;
18+
19+
return <div data-pw="ppr-postponed">{formatTime(data.unixTimeMs)}</div>;
20+
}
21+
22+
function Skeleton(): JSX.Element {
23+
return <div data-pw="ppr-prerendered">Skeleton</div>;
24+
}
25+
26+
export default function Page(): JSX.Element {
27+
return (
28+
<main>
29+
<h3>Partial Prerendering</h3>
30+
<Suspense fallback={<Skeleton />}>
31+
<ActualData />
32+
</Suspense>
33+
</main>
34+
);
35+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { notFound } from 'next/navigation';
2+
import { Suspense } from 'react';
23
import type { RandomHexPageProps } from 'cache-testing/utils/types';
4+
import { CacheStateWatcher } from 'cache-testing/components/cache-state-watcher';
5+
import { PreRenderedAt } from 'cache-testing/components/pre-rendered-at';
36

47
const lengthSteps = new Array(5).fill(0).map((_, i) => 10 ** (i + 1));
58

@@ -10,21 +13,29 @@ export function generateStaticParams(): PageParams['params'][] {
1013
}
1114

1215
export default async function Page({ params: { length } }: PageParams): Promise<JSX.Element> {
13-
const path = `/randomHex/${length}`;
16+
const path = `/randomHex/app/${length}`;
1417

1518
const url = new URL(path, 'http://localhost:8081');
1619

17-
const result = await fetch(url);
20+
const result = await fetch(url, {
21+
next: {
22+
tags: [`/app/randomHex/${length}`],
23+
},
24+
});
1825

1926
if (!result.ok) {
2027
notFound();
2128
}
2229

23-
const { randomHex } = (await result.json()) as RandomHexPageProps;
30+
const props = (await result.json()) as RandomHexPageProps;
2431

2532
return (
2633
<div>
27-
<div data-pw="randomHex">{randomHex}</div>
34+
<div data-pw="random-hex">{props.randomHex}</div>
35+
<PreRenderedAt time={props.unixTimeMs} />
36+
<Suspense fallback={null}>
37+
<CacheStateWatcher revalidateAfter={Infinity} time={props.unixTimeMs} />
38+
</Suspense>
2839
</div>
2940
);
3041
}

apps/cache-testing/src/components/cache-state-watcher.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,8 @@ export function CacheStateWatcher({ time, revalidateAfter }: CacheStateWatcherPr
3434
};
3535
}, [revalidateAfter, time]);
3636

37-
const formattedTime = new Date(time).toLocaleTimeString('ru-RU', {
38-
fractionalSecondDigits: 3,
39-
hour: '2-digit',
40-
minute: '2-digit',
41-
second: '2-digit',
42-
});
43-
4437
return (
4538
<div>
46-
<div data-pw="pre-rendered-at">Pre-rendered at {formattedTime}</div>
4739
<div data-pw="cache-state">Cache state: {cacheState}</div>
4840
<div data-pw="stale-after">Stale after: {countDown}</div>
4941
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { formatTime } from 'cache-testing/utils/format-time';
2+
3+
type CacheStateWatcherProps = { time: number };
4+
5+
export function PreRenderedAt({ time }: CacheStateWatcherProps): JSX.Element {
6+
const preRenderTime = formatTime(time, 3);
7+
8+
return <div data-pw="pre-rendered-at">Pre-rendered at {preRenderTime}</div>;
9+
}

apps/cache-testing/src/pages/api/revalidate-pages.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { NextApiRequest, NextApiResponse } from 'next';
2+
import { formatTime } from 'cache-testing/utils/format-time';
23

34
export default async function handler(request: NextApiRequest, result: NextApiResponse): Promise<void> {
45
const { path } = request.query;
@@ -17,12 +18,7 @@ export default async function handler(request: NextApiRequest, result: NextApiRe
1718
await result.revalidate(path);
1819
result.json({
1920
revalidated: true,
20-
now: new Date().toLocaleTimeString('ru-RU', {
21-
fractionalSecondDigits: 3,
22-
hour: '2-digit',
23-
minute: '2-digit',
24-
second: '2-digit',
25-
}),
21+
now: formatTime(Date.now(), 3),
2622
});
2723
} catch (err) {
2824
result.status(500).send('Error revalidating');

apps/cache-testing/src/pages/pages/randomHex/[length].tsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { GetStaticPathsResult, GetStaticPropsContext, GetStaticPropsResult } from 'next';
22
import type { RandomHexPageProps } from 'cache-testing/utils/types';
3+
import { CacheStateWatcher } from 'cache-testing/components/cache-state-watcher';
4+
import { PreRenderedAt } from 'cache-testing/components/pre-rendered-at';
35

46
const lengthSteps = new Array(5).fill(0).map((_, i) => 10 ** (i + 1));
57

@@ -16,19 +18,19 @@ export async function getStaticProps({
1618
throw new Error('no length');
1719
}
1820

19-
const pathAndTag = `/randomHex/${length}`;
21+
const path = `/randomHex/pages/${length}`;
2022

21-
const url = new URL(pathAndTag, 'http://localhost:8081');
23+
const url = new URL(path, 'http://localhost:8081');
2224

2325
const result = await fetch(url);
2426

2527
if (!result.ok) {
2628
return { notFound: true };
2729
}
2830

29-
const { randomHex } = (await result.json()) as { randomHex: string };
31+
const props = (await result.json()) as RandomHexPageProps;
3032

31-
return { props: { randomHex } };
33+
return { props };
3234
}
3335

3436
export function getStaticPaths(): Promise<GetStaticPathsResult> {
@@ -38,10 +40,12 @@ export function getStaticPaths(): Promise<GetStaticPathsResult> {
3840
});
3941
}
4042

41-
export default function Page({ randomHex }: RandomHexPageProps): JSX.Element {
43+
export default function Page({ randomHex, unixTimeMs }: RandomHexPageProps): JSX.Element {
4244
return (
4345
<div>
44-
<div data-pw="randomHex">{randomHex}</div>
46+
<div data-pw="random-hex">{randomHex}</div>
47+
<PreRenderedAt time={unixTimeMs} />
48+
<CacheStateWatcher revalidateAfter={Infinity} time={unixTimeMs} />
4549
</div>
4650
);
4751
}

apps/cache-testing/src/utils/common-app-page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Suspense } from 'react';
22
import { CacheStateWatcher } from 'cache-testing/components/cache-state-watcher';
3+
import { PreRenderedAt } from 'cache-testing/components/pre-rendered-at';
34
import type { PageProps } from './types';
45

56
export function CommonAppPage({ count, revalidateAfter, time, path }: PageProps): JSX.Element {
@@ -8,6 +9,7 @@ export function CommonAppPage({ count, revalidateAfter, time, path }: PageProps)
89
<div data-pw="data" id={path}>
910
{count}
1011
</div>
12+
<PreRenderedAt time={time} />
1113
<Suspense fallback={null}>
1214
<CacheStateWatcher revalidateAfter={revalidateAfter} time={time} />
1315
</Suspense>

apps/cache-testing/src/utils/common-pages-page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CacheStateWatcher } from 'cache-testing/components/cache-state-watcher';
2+
import { PreRenderedAt } from 'cache-testing/components/pre-rendered-at';
23
import type { PageProps } from './types';
34

45
export function CommonPagesPage({ count, revalidateAfter, time, path }: PageProps): JSX.Element {
@@ -7,6 +8,7 @@ export function CommonPagesPage({ count, revalidateAfter, time, path }: PageProp
78
<div data-pw="data" id={path}>
89
{count}
910
</div>
11+
<PreRenderedAt time={time} />
1012
<CacheStateWatcher revalidateAfter={revalidateAfter} time={time} />
1113
</div>
1214
);

0 commit comments

Comments
 (0)