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. # 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 # 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). # 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 ENV BUILD_KAYNAK_KISITLI=1
RUN --mount=type=cache,id=next-cache,target=/app/.next/cache \ RUN --mount=type=cache,id=next-cache,target=/app/.next/cache \
pnpm build pnpm build

Binary file not shown.

View File

@@ -30,8 +30,10 @@ const nextConfig: NextConfig = {
...(kaynakKisitli ...(kaynakKisitli
? { ? {
// Turbopack'ın Rust tarafı NODE_OPTIONS heap sınırını görmez; bellek // Turbopack'ın Rust tarafı NODE_OPTIONS heap sınırını görmez; bellek
// hedefi byte cinsinden ayrıca verilir (1.5 GB). // hedefi byte cinsinden ayrıca verilir (1 GB). 1.5 GB hedef + Node
turbopackMemoryLimit: 1_610_612_736, // 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 // Statik üretim: varsayılan 4 worker × 8 eşzamanlı sayfa yerine tek
// worker × 4 sayfa. Her worker ayrı bir Node süreci/heap'idir. // worker × 4 sayfa. Her worker ayrı bir Node süreci/heap'idir.
cpus: 1, cpus: 1,

View File

@@ -8,7 +8,7 @@
// üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir. // üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir.
import { useEffect, useMemo, useState } from "react"; 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 { useReducedMotion } from "motion/react";
import useMeasure from "react-use-measure"; import useMeasure from "react-use-measure";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -27,11 +27,14 @@ export function Cip({
secili, secili,
onClick, onClick,
children, children,
kucuk = false,
}: { }: {
secili: boolean; secili: boolean;
onClick: () => void; onClick: () => void;
children: React.ReactNode; children: React.ReactNode;
kucuk?: boolean;
}) { }) {
const boyut = kucuk ? "px-2.5 py-1 text-xs" : "px-3.5 py-2 text-sm";
return ( return (
<button <button
type="button" type="button"
@@ -39,8 +42,8 @@ export function Cip({
aria-pressed={secili} aria-pressed={secili}
className={ className={
secili 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-primary bg-primary font-medium text-white transition-colors duration-200 ${boyut}`
: "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-slate-200 bg-white text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 ${boyut}`
} }
> >
{children} {children}
@@ -103,6 +106,26 @@ export function SihirbazAdimlar({
() => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)), () => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)),
[siraliIller], [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( const haritaIlleri = useMemo(
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)), () => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
[canliFacetler.iller], [canliFacetler.iller],
@@ -266,45 +289,79 @@ export function SihirbazAdimlar({
En fazla 5 il seçebilirsin. İstersen boş bırak, fark etmez. En fazla 5 il seçebilirsin. İstersen boş bırak, fark etmez.
</p> </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 {/* Çipler birincil seçim yolu (haritadaki küçük iller mobilde
güvenilir dokunma hedefi değil); iller seçili kategorilere göre 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 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 <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" : "" facetYukleniyor ? "animate-pulse opacity-60" : ""
}`} }`}
> >
{turkiyeIlleri.map((i) => ( {gorunenTurkiyeIlleri.map((i) => (
<Cip <Cip
key={i.il} key={i.il}
kucuk
secili={iller.includes(i.il)} secili={iller.includes(i.il)}
onClick={() => listeDegistir(iller, setIller, i.il, 5)} onClick={() => listeDegistir(iller, setIller, i.il, 5)}
> >
{trBaslikDuzeni(i.il)} {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> </Cip>
))} ))}
</div> </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"> <div className="mt-4">
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase"> <p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
KKTC & Yurt Dışı KKTC & Yurt Dışı
</p> </p>
<div <div
className={`flex flex-wrap gap-2 ${ className={`flex flex-wrap gap-1.5 ${
facetYukleniyor ? "animate-pulse opacity-60" : "" facetYukleniyor ? "animate-pulse opacity-60" : ""
}`} }`}
> >
{disIller.map((i) => ( {gorunenDisIller.map((i) => (
<Cip <Cip
key={i.il} key={i.il}
kucuk
secili={iller.includes(i.il)} secili={iller.includes(i.il)}
onClick={() => listeDegistir(iller, setIller, i.il, 5)} onClick={() => listeDegistir(iller, setIller, i.il, 5)}
> >
{trBaslikDuzeni(i.il)} {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> </Cip>
))} ))}
</div> </div>