feat: enable React Compiler, add Redis caching for API routes, update README with requirements and install guide
Local Build and Deploy / deploy (push) Failing after 57s
Local Build and Deploy / deploy (push) Failing after 57s
This commit is contained in:
1 parent
f53d25e0a5
commit
ccd4994028
5 files changed
+227
-246
No files matched your search
@@ -45,6 +45,9 @@ const nextConfig: NextConfig = {
|
|||||||
"pino-pretty",
|
"pino-pretty",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Enable React Compiler for automatic memoization
|
||||||
|
reactCompiler: true,
|
||||||
|
|
||||||
// Compress responses with gzip
|
// Compress responses with gzip
|
||||||
compress: true,
|
compress: true,
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { apiJson } from "@/lib/api";
|
import { apiJson } from "@/lib/api";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redisCache, apiCacheKey } from "@/lib/redis-cache";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -10,12 +11,12 @@ export const dynamic = "force-dynamic";
|
|||||||
* used by the public pages and the admin dashboard.
|
* used by the public pages and the admin dashboard.
|
||||||
*/
|
*/
|
||||||
export async function GET(_req: Request) {
|
export async function GET(_req: Request) {
|
||||||
let articles: unknown[] = [];
|
|
||||||
let online = 0;
|
|
||||||
let hotelName = "Atom";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[articles, online, hotelName] = await Promise.all([
|
const data = await redisCache(
|
||||||
|
apiCacheKey("home"),
|
||||||
|
15,
|
||||||
|
async () => {
|
||||||
|
const [articles, online, hotelName] = await Promise.all([
|
||||||
prisma.websiteArticles.findMany({
|
prisma.websiteArticles.findMany({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -31,10 +32,11 @@ export async function GET(_req: Request) {
|
|||||||
prisma.user.count({ where: { online: "1" } }),
|
prisma.user.count({ where: { online: "1" } }),
|
||||||
siteSettings.get("hotel_name", "Atom").then((v) => v ?? "Atom"),
|
siteSettings.get("hotel_name", "Atom").then((v) => v ?? "Atom"),
|
||||||
]);
|
]);
|
||||||
|
return { articles, online, hotelName };
|
||||||
return apiJson({ articles, online, hotelName });
|
},
|
||||||
|
);
|
||||||
|
return apiJson(data);
|
||||||
} catch {
|
} catch {
|
||||||
// DB unavailable — return an empty payload instead of a 500.
|
return apiJson({ articles: [], online: 0, hotelName: "Atom" }, { status: 200 });
|
||||||
return apiJson({ articles: [], online: 0, hotelName }, { status: 200 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { apiJson } from "@/lib/api";
|
import { apiJson } from "@/lib/api";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redisCache, apiCacheKey } from "@/lib/redis-cache";
|
||||||
|
|
||||||
// Radio player config: the subset of radio_* website_settings the front-end
|
// Radio player config: the subset of radio_* website_settings the front-end
|
||||||
// player needs (stream URL, name, autoplay, enabled, widget visibility) as a
|
// player needs (stream URL, name, autoplay, enabled, widget visibility) as a
|
||||||
@@ -27,19 +28,23 @@ const CONFIG_KEYS = new Set([
|
|||||||
|
|
||||||
export async function GET(_req: Request) {
|
export async function GET(_req: Request) {
|
||||||
try {
|
try {
|
||||||
|
const config = await redisCache(
|
||||||
|
apiCacheKey("radio:config"),
|
||||||
|
30,
|
||||||
|
async () => {
|
||||||
const rows = await prisma.websiteSetting.findMany({
|
const rows = await prisma.websiteSetting.findMany({
|
||||||
where: { key: { in: Array.from(CONFIG_KEYS) } },
|
where: { key: { in: Array.from(CONFIG_KEYS) } },
|
||||||
select: { key: true, value: true },
|
select: { key: true, value: true },
|
||||||
});
|
});
|
||||||
|
const result: Record<string, string> = {};
|
||||||
const config: Record<string, string> = {};
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
config[row.key] = row.value;
|
result[row.key] = row.value;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
);
|
||||||
return apiJson(config);
|
return apiJson(config);
|
||||||
} catch {
|
} catch {
|
||||||
// DB unavailable — serve an empty config rather than a 500.
|
|
||||||
return apiJson({}, { status: 200 });
|
return apiJson({}, { status: 200 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import "server-only";
|
||||||
|
|
||||||
|
import { redis } from "@/lib/redis";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the result of a fetch function in Redis.
|
||||||
|
* Falls back to the fresh fetch if Redis is unavailable.
|
||||||
|
*/
|
||||||
|
export async function redisCache<T>(
|
||||||
|
key: string,
|
||||||
|
ttlSeconds: number,
|
||||||
|
fetch: () => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
if (!redis) return fetch();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cached = await redis.get(key);
|
||||||
|
if (cached !== null) {
|
||||||
|
return JSON.parse(cached) as T;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// cache miss or error — fall through to fresh fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
const fresh = await fetch();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await redis.setex(key, ttlSeconds, JSON.stringify(fresh));
|
||||||
|
} catch {
|
||||||
|
// ignore write errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate a cached key. No-op if Redis is unavailable.
|
||||||
|
*/
|
||||||
|
export async function invalidateCache(key: string): Promise<void> {
|
||||||
|
if (!redis) return;
|
||||||
|
try {
|
||||||
|
await redis.del(key);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a namespaced cache key for API routes.
|
||||||
|
*/
|
||||||
|
export function apiCacheKey(path: string): string {
|
||||||
|
return `api:${path}`;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user