chore: update memory limits in Dockerfile and next.config.ts
All checks were successful
Deploy / deploy (push) Successful in 7m21s

- Reduced NODE_OPTIONS max-old-space-size from 1536 to 1024 MB in Dockerfile.
- Adjusted turbopackMemoryLimit in next.config.ts from 1.5 GB to 1 GB to prevent OOM-kills during build on VPS with limited memory.
- Added a search input for filtering Turkish provinces in the SihirbazAdimlar component, improving user experience with real-time search functionality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-09 00:33:13 +03:00
parent e0ff9dec0a
commit 24f53ddbce
4 changed files with 73 additions and 14 deletions

View File

@@ -33,7 +33,7 @@ ENV NEXT_TELEMETRY_DISABLED=1
# V8 heap sınırı ana süreç + statik üretim worker'larına miras kalır.
# Turbopack'ın Rust tarafı bu sınırın dışında; onun hedefi next.config.ts'te
# turbopackMemoryLimit ile verilir (BUILD_KAYNAK_KISITLI bloğu).
ENV NODE_OPTIONS="--max-old-space-size=1536"
ENV NODE_OPTIONS="--max-old-space-size=1024"
ENV BUILD_KAYNAK_KISITLI=1
RUN --mount=type=cache,id=next-cache,target=/app/.next/cache \
pnpm build

Binary file not shown.

View File

@@ -30,8 +30,10 @@ const nextConfig: NextConfig = {
...(kaynakKisitli
? {
// Turbopack'ın Rust tarafı NODE_OPTIONS heap sınırını görmez; bellek
// hedefi byte cinsinden ayrıca verilir (1.5 GB).
turbopackMemoryLimit: 1_610_612_736,
// hedefi byte cinsinden ayrıca verilir (1 GB). 1.5 GB hedef + Node
// heap'i, ~1.5 GB boş belleği olan VPS'te derleme fazında OOM-kill
// üretiyordu (lokalde --memory=1536m ile reprodüke edildi).
turbopackMemoryLimit: 1_073_741_824,
// Statik üretim: varsayılan 4 worker × 8 eşzamanlı sayfa yerine tek
// worker × 4 sayfa. Her worker ayrı bir Node süreci/heap'idir.
cpus: 1,

View File

@@ -8,7 +8,7 @@
// üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir.
import { useEffect, useMemo, useState } from "react";
import { ArrowLeft, ArrowRight, X } from "lucide-react";
import { ArrowLeft, ArrowRight, Search, X } from "lucide-react";
import { useReducedMotion } from "motion/react";
import useMeasure from "react-use-measure";
import { toast } from "sonner";
@@ -27,11 +27,14 @@ export function Cip({
secili,
onClick,
children,
kucuk = false,
}: {
secili: boolean;
onClick: () => void;
children: React.ReactNode;
kucuk?: boolean;
}) {
const boyut = kucuk ? "px-2.5 py-1 text-xs" : "px-3.5 py-2 text-sm";
return (
<button
type="button"
@@ -39,8 +42,8 @@ export function Cip({
aria-pressed={secili}
className={
secili
? "cursor-pointer rounded-full border border-primary bg-primary px-3.5 py-2 text-sm font-medium text-white transition-colors duration-200"
: "cursor-pointer rounded-full border border-slate-200 bg-white px-3.5 py-2 text-sm text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50"
? `cursor-pointer rounded-full border border-primary bg-primary font-medium text-white transition-colors duration-200 ${boyut}`
: `cursor-pointer rounded-full border border-slate-200 bg-white text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 ${boyut}`
}
>
{children}
@@ -103,6 +106,26 @@ export function SihirbazAdimlar({
() => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)),
[siraliIller],
);
// İl araması: Türkçe büyük/küçük harf ve şapkalı harf farklarına duyarsız
// eşleşme için iki taraf da normalizeIlAdi'dan geçirilir.
const [ilArama, setIlArama] = useState("");
const ilAramaNorm = normalizeIlAdi(ilArama.trim());
const gorunenTurkiyeIlleri = useMemo(
() =>
ilAramaNorm
? turkiyeIlleri.filter((i) =>
normalizeIlAdi(i.il).includes(ilAramaNorm),
)
: turkiyeIlleri,
[turkiyeIlleri, ilAramaNorm],
);
const gorunenDisIller = useMemo(
() =>
ilAramaNorm
? disIller.filter((i) => normalizeIlAdi(i.il).includes(ilAramaNorm))
: disIller,
[disIller, ilAramaNorm],
);
const haritaIlleri = useMemo(
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
[canliFacetler.iller],
@@ -266,45 +289,79 @@ export function SihirbazAdimlar({
En fazla 5 il seçebilirsin. İstersen boş bırak, fark etmez.
</p>
<div className="mt-4 flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 py-2 transition-colors focus-within:border-slate-300">
<Search className="size-4 shrink-0 text-slate-400" aria-hidden />
<input
type="text"
value={ilArama}
onChange={(e) => setIlArama(e.target.value)}
placeholder="İl ara..."
aria-label="İl ara"
autoComplete="off"
className="min-w-0 flex-1 bg-transparent text-sm text-slate-950 outline-none placeholder:text-slate-400"
/>
{ilArama ? (
<button
type="button"
onClick={() => setIlArama("")}
aria-label="Aramayı temizle"
className="flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
>
<X className="size-3.5" aria-hidden />
</button>
) : null}
</div>
{/* Çipler birincil seçim yolu (haritadaki küçük iller mobilde
güvenilir dokunma hedefi değil); iller seçili kategorilere göre
süzülmüş, program sayısına göre sıralı listelenir. Penceresinde
programı olan TÜM iller görünür (kırpma yok). */}
programı olan TÜM iller görünür (kırpma yok); arama yalnızca
görünümü daraltır, seçimleri etkilemez. */}
<div
className={`mt-4 flex max-h-56 flex-wrap gap-2 overflow-y-auto sm:max-h-none sm:overflow-visible ${
className={`mt-3 flex max-h-56 flex-wrap gap-1.5 overflow-y-auto sm:max-h-none sm:overflow-visible ${
facetYukleniyor ? "animate-pulse opacity-60" : ""
}`}
>
{turkiyeIlleri.map((i) => (
{gorunenTurkiyeIlleri.map((i) => (
<Cip
key={i.il}
kucuk
secili={iller.includes(i.il)}
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
>
{trBaslikDuzeni(i.il)}
<span className="ml-1.5 text-xs opacity-70">{i.adet}</span>
<span className="ml-1 text-[10px] opacity-70">{i.adet}</span>
</Cip>
))}
</div>
{disIller.length > 0 ? (
{ilAramaNorm &&
gorunenTurkiyeIlleri.length === 0 &&
gorunenDisIller.length === 0 ? (
<p className="mt-3 text-sm text-slate-500">
&ldquo;{ilArama.trim()}&rdquo; ile eşleşen il yok.
</p>
) : null}
{gorunenDisIller.length > 0 ? (
<div className="mt-4">
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
KKTC & Yurt Dışı
</p>
<div
className={`flex flex-wrap gap-2 ${
className={`flex flex-wrap gap-1.5 ${
facetYukleniyor ? "animate-pulse opacity-60" : ""
}`}
>
{disIller.map((i) => (
{gorunenDisIller.map((i) => (
<Cip
key={i.il}
kucuk
secili={iller.includes(i.il)}
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
>
{trBaslikDuzeni(i.il)}
<span className="ml-1.5 text-xs opacity-70">{i.adet}</span>
<span className="ml-1 text-[10px] opacity-70">{i.adet}</span>
</Cip>
))}
</div>