Sıfırdan Bir Web Sitesi Kurmak — Hiçbir Şey Bilmeyen Birinin Anlattığı Şekliyle

1. Günde bu blogda hangi teknolojileri neden kullanacağımı anlattım — henüz elle tutulur olmayan, biraz uzaktaki bir gelecek gibiydi. Bu yazıda ise geriye, en başa dönüyorum: bu sitenin kendisini sıfırdan nasıl kurduğumu, hiçbir şey bilmeyen birine anlatır gibi, adım adım anlatıyorum.

Genelde teknik yazılar “şunu kurdum” der ve devam eder. Ben bunun tersini yapmak istiyorum: bu yazıyı okuyan, hiçbir şey bilmeyen biri, okuyunca kafasında bir şeyler canlanabilsin istiyorum. O yüzden bugün bir web sitesini internete koyarken kullandığım her kavramı önce en basit haliyle tanımlayacağım, sonra da o kavramın tam olarak nerede işime yaradığını ve nerede beni hataya düşürdüğünü anlatacağım. Toplamda altı hata yaptım, hepsi ayrı bir ders oldu.

Önce en temel soru: Bir web sitesi aslında nedir?

Tarayıcına bir adres yazdığında (mesela google.com), aslında şunlar olur: bilgisayarın “bu adres nerede duruyor, hangi bilgisayarda?” diye bir soru sorar. Bu soruya cevap veren sisteme birazdan geleceğim (DNS). Cevap geldiğinde, tarayıcın o uzak bilgisayara bağlanıp “bana sayfanı gönder” der. Uzak bilgisayar da bir HTML dosyası gönderir, tarayıcın bu dosyayı okuyup ekrana çizer.

Yani bir web sitesi, aslında bir yerlerde duran bir dosya (ya da dosyalar) ve o dosyaya “her isteyene gönder” diyen bir sistemden ibaret. Bugün yaptığım her şey, aslında bu iki şeyi kurmaktan ibaretti: bir dosya yazmak, ve o dosyayı “herkese açık” hale getirmek.

Terminal nedir, neden fare yerine yazı kullanıyorum?

Bilgisayarında genelde bir şeye tıklayarak işlem yaparsın. Terminal (PowerShell de bunun Windows’taki hali) ise tıklama yerine yazarak komut verdiğin bir ekran. Neden buna ihtiyacım var? Çünkü bugün kullandığım araçların çoğu (Git, Astro, Cloudflare’in araçları) görsel bir arayüze sahip değil, sadece komutla konuşuyor. İlk başta ürkütücü görünüyor ama aslında her komut, bir cümlenin kısaltılmış hali. cd blog demek “blog klasörüne git” demek, npm run build demek “projeyi derle” demek. Zamanla bu kısaltmalar tanıdık gelmeye başlıyor.

Git ve GitHub: Kodun hafızası

Bir belgeyi Word’de yazarken “Kaydet” dersin, üstüne yazılır, eski hali gider. Git ise her kaydettiğinde eski hali de saklayan bir sistem; sanki her kaydettiğinde belgenin o anki fotoğrafını çekiyormuşsun gibi düşün. Bu fotoğraflara “commit” deniyor. GitHub ise bu fotoğrafların internette, bulutta durduğu yer — kendi bilgisayarın bozulsa bile kaybolmayan bir yedek, aynı zamanda başkalarıyla paylaşabileceğin bir yer.

Bugün yaptığım ilk şey, bilgisayarımda bir blog klasörü açıp içinde git init komutunu çalıştırmaktı. Bu komut, o klasöre “bundan sonra burada olan her değişikliği fotoğraflayabilirim” demek gibi bir şey. Sonra GitHub’da boş bir “repo” (depo, yani proje klasörünün bulut karşılığı) açtım ve bilgisayarımdaki klasörü ona bağladım.

Birinci hatam burada oldu. Klasörü GitHub’daki depoya bağlarken şu komutu kullanman gerekiyor:

git remote add origin https://github.com/KULLANICI_ADIN/blog.git

Ben KULLANICI_ADIN yazan yeri kendi adımla değiştireceğime, örnek metni olduğu gibi bıraktım. Sonuç: Repository not found hatası, çünkü öyle bir kullanıcı gerçekten yok. Düzeltmesi basitti, doğru adresi yazdım. Ama bir kafa karışıklığı daha oldu: GitHub’da tarayıcıda gördüğüm adres github.com/idrishannn/blog iken, Git’in istediği adresin sonunda .git vardı: github.com/idrishannn/blog.git. Bu .git uzantısı, tıpkı bir dosyanın .pdf ya da .jpg uzantısı gibi, “bu bir Git deposu” demek. Tarayıcı sana sayfayı gösterirken bu uzantıyı göstermiyor ama Git bunu istiyor. Yani blog.git diye ayrı bir depo yok, sadece Git’in konuştuğu “dil” farklı.

Claude Code: Terminalden AI ile çalışmak

Claude Code, terminale kurduğum ve orada benimle konuşan bir yapay zeka aracı. Ben kod yazmayı bilmiyorum, o yüzden “şunu yap” diyorum, o yazıyor, ben de yazdığını bana açıklattırıyorum. Amacım kod yazmayı öğrenmek değil, ne yapıldığını anlayabilmek.

İkinci hatam kurulumdaydı. Kurulum programı Claude Code’u bilgisayarıma başarıyla koydu ama terminal onu bulamadı: 'claude' tanınmayan bir komut dedi. Sebebi şuydu: Windows, bir komut yazdığında hangi klasörlerde arama yapacağını bilen bir liste tutuyor, buna PATH deniyor. Kurulum programı Claude Code’u bir klasöre koydu ama bu klasörü o listeye eklemeyi tam yapamamıştı. Listeye elle ekledim, ama yine de çalışmadı — çünkü PATH listesi sadece terminal yeni açıldığında okunuyor, açık olan pencereler eski listeyle çalışmaya devam ediyor. Terminali kapatıp yeniden açınca sorun çözüldü. Küçük ama önemli bir ders: bazı ayarlar hemen değil, yeni bir “oturumda” etkili oluyor.

İlk dosyam ve “deploy” kavramı

Claude Code’a basit bir index.html dosyası yazdırdım — sadece adım ve kısa bir tanıtım yazan tek sayfalık bir dosya. Bu dosya bilgisayarımda durduğu sürece sadece ben görebiliyorum. Onu herkesin görebileceği hale getirmeye “deploy etmek” (yayına almak) deniyor.

Cloudflare Workers: Dosyamı internete koyduğum yer

Bir dosyayı internette herkese açık hale getirmek için, o dosyayı 7/24 açık, internete bağlı bir bilgisayarda tutman lazım. Bunu kendi bilgisayarınla yapmak (hep açık tutmak, güvenliğini sağlamak) zahmetli olduğu için, bu işi yapan şirketlere bu iş için para ya da (küçük projelerde) bedava yer veriyorsun. Cloudflare da böyle bir şirket — 1. Günde neden onu seçtiğimi zaten anlatmıştım.

Üçüncü hatam (aslında hata bile değildi) burada oldu. Ben Cloudflare’in “Pages” adlı ürününü kullanacağımı sanıyordum, adresim de blog.pages.dev gibi bir şey olacaktı. Ama GitHub deposunu bağlayıp yayınladığımda adresim blog.idrishanndemirdag.workers.dev çıktı — yani “Pages” değil “Workers” adı geçiyordu. Araştırınca öğrendim ki Cloudflare 2026 içinde Pages ürününü kademeli olarak Workers adlı başka bir ürünün içine taşıyor; yeni projeler artık direkt Workers üzerinden açılıyor. Yani hata falan yapmamıştım, sadece kullandığım aracın kendisi benim haberim olmadan değişmişti. Bu bana ilginç bir ders verdi: bazen “yanlış yapıyorum” sandığın şey, aslında altındaki zeminin kaymasından kaynaklanıyor.

Domain almak: idrishanlabs.com

blog.idrishanndemirdag.workers.dev çalışan ama akılda kalıcı olmayan bir adres. İnsanların hatırlayabileceği, kısa bir adres almak istedim: idrishanlabs.com.

Burada domain’in ne olduğunu netleştirmek lazım. Domain, bir ev adresi gibi düşünülebilir: gerçekte sitenin dosyaları Cloudflare’in bir bilgisayarında duruyor (buna “sunucu” deniyor), ama insanların o karmaşık teknik adresi ezberlemesine gerek kalmasın diye, kolay hatırlanan bir isim (idrishanlabs.com) o gerçek adrese yönlendiriliyor.

Domain’i Namecheap adlı bir şirketten satın aldım. Burada dikkat ettiğim birkaç şey oldu: ek paketleri (hosting, e-posta gibi) almadım çünkü onları zaten Cloudflare bedava veriyor; gizlilik korumasının (WHOIS Privacy) açık olduğundan emin oldum, çünkü bu olmazsa adın ve adresin gibi bilgiler herkese açık bir veri tabanında görünebiliyor.

DNS ve nameserver: Bir domain’i bir siteye nasıl bağlarsın?

Domain’i satın almak, onu otomatik olarak sitene bağlamıyor. Burada devreye DNS giriyor. DNS’i şöyle düşün: bir telefon rehberi. Sen birinin ismini biliyorsun (idrishanlabs.com) ama telefon numarasını (yani gerçek teknik adresini) bilmiyorsun. DNS, bu ismi alıp doğru numarayı sana veren rehber.

Peki bu rehberi kim tutuyor? İşte “nameserver” burada devreye giriyor. Bir domain’i satın aldığın yer (Namecheap) varsayılan olarak bu rehberi kendisi tutuyor. Ama ben bu rehberi Cloudflare’in tutmasını istedim, çünkü sitem zaten Cloudflare’de duruyor. Bu yüzden Namecheap panelinde domain’imin “nameserver” ayarını değiştirip, Cloudflare’in bana verdiği iki adresi yazdım:

ines.ns.cloudflare.com
lakas.ns.cloudflare.com

Bu değişikliğin dünya genelindeki tüm DNS sunucularına yayılması (yani herkesin “artık bu domain’in rehberini Cloudflare tutuyor” bilgisini öğrenmesi) birkaç saat sürüyor. Bu bekleme, internetin merkezi olmayan yapısının bir sonucu — hiçbir yerde “hepsini aynı anda güncelleyen tek bir düğme” yok, bilgi kademe kademe yayılıyor.

Bu arada Cloudflare bana “botların sitene ne yapmasına izin veriyorsun” diye bir soru sordu (arama motorları taramasın mı, AI asistanları okuyabilsin mi, AI eğitim verisi olarak kullanılsın mı gibi). Hepsini varsayılan haliyle bıraktım, çünkü blogumun bulunabilir olmasını istiyorum. Sonra bir “plan” seçmem istendi (Free, Pro, Business, Enterprise). Free’yi seçtim; içinde sınırsız statik site, SSL sertifikası (sitenin adresi başında kilit işareti çıkmasını sağlayan güvenlik katmanı) ve DNS dahildi. Kişisel bir blog için diğer planlara hiç gerek yok.

Astro’ya geçiş: Tek sayfadan, büyüyebilen bir siteye

İlk index.html dosyam tek bir sayfaydı. Ama ben her gün yeni bir yazı ekleyeceğim, her seferinde elle HTML yazmak hem yorucu hem hataya açık olurdu. Bunun için Astro adlı bir araç kullandım. Astro, sen düz metin dosyaları (Markdown, .md uzantılı) yazınca, onları otomatik olarak güzel görünen web sayfalarına çeviren bir sistem. Buna “statik site üretici” deniyor. Bu sayede benim işim sadece yeni bir metin dosyası açıp yazmak, tasarımla uğraşmama gerek kalmıyor.

Dördüncü hata burada oldu. Astro’yu kurmak için şu komutu çalıştırdım:

npm create astro@latest . -- --template blog

Buradaki tek nokta (.) “bu komutu şu an bulunduğum klasöre kur” demek. Ama klasörümde zaten index.html ve .git gibi dosyalar olduğu için, Astro “bu klasör boş değil” deyip kendi projesini steadfast-sphere adında rastgele isimli, yeni bir alt klasörde oluşturdu. Yani projem blog klasöründe değil, blog/steadfast-sphere içinde açılmıştı — GitHub bağlantımın olduğu yerden ayrı bir yerde.

Çözümü, bu alt klasördeki her şeyi bir üst klasöre (yani blog’un köküne) taşımaktı. Bunun için robocopy adlı bir Windows aracını kullandım; bu araç dosyaları (gizli olanlar dahil) güvenli bir şekilde bir yerden başka bir yere taşıyor. Taşıma bitince boş kalan steadfast-sphere klasörünü sildim.

wrangler.jsonc: Cloudflare’e “neyi nereden alacağını” söyleyen dosya

Astro projemi Cloudflare’e yayınlamak için, Cloudflare’in “bu projenin yayınlanacak dosyaları nerede duruyor” bilgisini bilmesi lazım. Bunun için proje köküne wrangler.jsonc adında bir dosya ekledim. İçinde üç önemli bilgi var:

  • Projenin adı ne (blog)
  • Hangi tarihin kurallarına göre çalışacağı (compatibility_date) — Cloudflare’in altyapısı zamanla değiştiği için, bu tarih “şu günün kurallarına göre çalış” diyor, böylece ileride gelen değişiklikler projemi habersizce bozmuyor.
  • Yayınlanacak dosyaların hangi klasörde olduğu (./dist)

“dist klasörü yok” hatası: Build nedir?

Astro’da yazdığım .md dosyaları, ham haliyle bir tarayıcının anlayacağı HTML değil. Bunları gerçek HTML’e çeviren işleme “build” (derleme) deniyor. Bunu şöyle düşünebilirsin: .md dosyaların hammadde, npm run build komutu bu hammaddeyi işleyen makine, dist klasörü de üretim hattının sonunda çıkan bitmiş ürün.

Beşinci hatam tam burada oldu. GitHub’a dosyalarımı gönderdikten (git push) sonra Cloudflare otomatik olarak siteyi yayınlamaya çalıştı ama şu hatayı verdi:

The directory specified by the "assets.directory" field ... does not exist: /opt/buildhome/repo/dist

Yani Cloudflare, dist klasörünü arıyordu ama böyle bir klasör yoktu. Sebebi, Cloudflare panelindeki “Build command” (derleme komutu) alanının boş kalmış olmasıydı — Cloudflare hammaddeyi işleyecek makineyi hiç çalıştırmamıştı, direkt bitmiş ürünü aramaya gitmişti. Panelde bu alana npm run build yazıp kaydettim, sorun çözüldü.

AI’ın yanıldığı an: Neden her şeyi sorgulamalıyım

Bu belki de günün en önemli dersiydi. Bir noktada Claude Code bana “ben index.html dosyasını silmedim, sen ya da başka biri yapmış olmalı, benim oturumumun başından sonra olmuş” dedi — oldukça emin bir tonla. Oysa gerçekte o dosyayı ben, kendi isteğimle, birkaç adım önce sildirmiştim. AI, kendi geçmiş adımlarını her zaman net hatırlamıyor ve bazen emin bir tonla yanlış bir sonuca varabiliyor.

Bunu fark edip düzeltmemin tek sebebi, gerçek durumu kendi gözümle kontrol etmemdi (dosya gezgininde klasöre bakıp index.html’in orada olmadığını görmüştüm). Eğer AI’ın söylediğine sorgusuz sualsiz inansaydım, olmayan bir sorunu çözmeye çalışıp vaktimi boşa harcayabilirdim. Bu yüzden artık kendime bir kural koydum: AI ne kadar emin konuşursa konuşsun, kontrol edebileceğim bir şeyi söylüyorsa, kontrol ederim.

Domain’i bağlarken çıkan altıncı hata

Birkaç gün sonra domain’in nameserver’ları yayılmasını tamamladı, Cloudflare panelinde durumu “Active” oldu. Sıra, idrishanlabs.com’u gerçekten siteme (Worker’ıma) bağlamaya gelmişti. Cloudflare panelinde “Add Domain” dediğimde şu hatayı aldım:

Hostname 'idrishanlabs.com' already has externally managed DNS records (A, CNAME, etc). Delete them first or try a different hostname.

Yani “bu domain için zaten DNS kayıtları var, önce onları sil” diyordu. Burada iki yeni kavramı öğrenmem gerekti: A kaydı, bir domain’in düz bir IP adresine (bir bilgisayarın internet üzerindeki gerçek “sokak adresi” gibi düşün) yönlendirilmesini sağlıyor. CNAME kaydı ise bir domain’i başka bir domain’e yönlendiriyor. DNS Records sayfasına bakınca sebebi gördüm: Cloudflare, domain’i “onboard” ederken Namecheap’teki eski ayarları (satın aldığım ama henüz bir siteye bağlamadığım domain’ler için Namecheap’in gösterdiği standart “parking page”) olduğu gibi içeri aktarmıştı. Bu eski kayıtlar, benim yeni Worker’ıma giden yolla çakışıyordu.

Listede sekiz kayıt vardı ama hepsini silmek yanlış olurdu. İki tanesini (A kaydı ve www için olan CNAME kaydı, ikisi de parkingpage.namecheap.com’a gidiyordu) sildim. Geri kalan beş MX kaydı ve bir TXT kaydına dokunmadım, çünkü onlar Namecheap’in ücretsiz e-posta yönlendirme servisine aitti — web sitemle hiç ilgisi yoktu, silersem belki ileride kuracağım sen@idrishanlabs.com gibi bir e-posta adresini bozabilirdi. Bu da bir ders oldu: bir listede “hepsini temizle” dürtüsüne kapılmadan önce, her satırın gerçekte ne işe yaradığını anlamak gerekiyor.

İki kaydı sildikten sonra “Add Domain”i tekrar denedim, bu sefer sorunsuz geçti. Birkaç dakika sonra https://idrishanlabs.com adresini açtığımda, adres çubuğunda kilit işaretiyle birlikte sitem karşımdaydı — artık workers.dev gibi uzun bir adres değil, kendi ismim.

Bugünden çıkardığım büyük resim

Elektronik mühendisliğinden geldiğim için bunu bir devre gibi düşünmeyi seviyorum: idrishanlabs.com bir kullanıcının bastığı düğme, DNS o düğmeyi doğru devreye yönlendiren anahtarlama sistemi, Cloudflare Workers o devrenin gerçekte çalıştığı yer, GitHub ise devrenin şemasının (kodun) saklandığı arşiv. Astro da bu şemayı okuyup çalışır hale getiren bir tür otomatik üretim hattı.

Bugün hiçbir şey ilk seferde düz gitmedi. Altı ayrı hata yaptım, ama her hatanın altında basit ve anlaşılır bir sebep vardı, ve her sebep bana bir kavram öğretti. Sanırım bu blogun ritmi böyle olacak: kurmak, bozulan yeri bulmak, neden bozulduğunu anlamak, düzeltmek.

Sırada ne var?

Artık temel ayakta: site yayında, kendi alan adımda, ve her gün yeni bir yazı ekleyebileceğim bir sisteme sahibim. Sıradaki adım, 0. Gündeki yol haritasının Faz 1’i: Cloudflare Workers üzerinde Hono ile küçük bir API kurup, tarayıcı ile sunucu arasındaki “konuşma dilini” (HTTP) daha yakından görmek. Onu da, bugün olduğu gibi, hiçbir şey bilmeyen birine anlatır gibi yazacağım.

Yorumlar