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

This commit is contained in:
openhands committed 2026-07-20 14:14:49 +02:00
1 parent f53d25e0a5
commit ccd4994028
5 files changed
+227 -246

No files matched your search

+150 -232
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -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,
+11 -9
View File
@@ -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 });
} }
} }
+10 -5
View File
@@ -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 });
} }
} }
+53
View File
@@ -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}`;
}