ΠελάτηςΣε παραγωγή2026

Ιδιωτικό ΚΕΠ e-support Ιωαννίνων

Lead Developer

Δίγλωσσο website για ιδιωτικό ΚΕΠ στα Ιωάννινα: 12 κατηγορίες υπηρεσιών, JSON-LD με ΓΕΜΗ/ΑΦΜ identifiers, AEO layer με build-time llms.txt και markdown mirror, και CI gates σε κάθε βήμα. Από τα design comps ως το Cloudflare production.

Stack

  • Astro 7
  • Tailwind v4
  • Cloudflare Workers
  • TypeScript
  • Playwright + vitest
  • llms.txt / AEO
  • JSON-LD
  • rorsa-platform forms
Ιδιωτικό ΚΕΠ e-support Ιωαννίνων

Το σύστημα

Το e-support είναι ιδιωτικό ΚΕΠ στα Ιωάννινα: αναλαμβάνει υποθέσεις ΕΦΚΑ, ΔΥΠΑ, ΑΑΔΕ, ΚΕΠΑ, επιδομάτων, πιστοποιητικών, οχημάτων και συντάξεων, με φυσική παρουσία στα Ιωάννινα και εξ αποστάσεως εξυπηρέτηση πανελλαδικά. Χρειαζόταν website που να εξηγεί καθαρά τι κάνει (και τι δεν είναι: δεν πρόκειται για δημόσια υπηρεσία), να στέκεται και στα ελληνικά και στα αγγλικά, και να βρίσκεται εύκολα τόσο από τη Google όσο και από AI assistants. Ξεκίνησα από το in-house Astro website-template και παρέδωσα ένα πλήρως στατικό, δίγλωσσο site με 12 κατηγορίες υπηρεσιών ως content collection, lead form, και ένα AEO layer που κρατάει τη machine-readable εικόνα του site πάντα συγχρονισμένη με το ίδιο το site.

Αρχιτεκτονική

  • Static Astro 7 + Tailwind v4: καθαρό στατικό output χωρίς adapter, TypeScript παντού, self-hosted Commissioner μέσω Astro Fonts API (κανένα third-party font request)
  • Δίγλωσσο i18n: ελληνικά στη ρίζα, αγγλικά κάτω από /en/, με ελληνικά slugs και στις δύο γλώσσες (/ypiresies/, όχι /en/services/). Ένα και μόνο helper (localePath()) χτίζει κάθε locale-prefixed href, και structural-parity test σπάει το build αν τα δύο λεξικά αποκλίνουν
  • Content collection με opt-in detail pages: οι 12 κατηγορίες υπηρεσιών είναι markdown αρχεία, και μια κατηγορία αποκτά δική της σελίδα-οδηγό μόνο όταν το αρχείο της έχει μη κενό body. Το ίδιο body-presence test τροφοδοτεί το getStaticPaths, τα links της λίστας και τον κατάλογο του llms.txt, οπότε τα τρία δεν μπορούν να διαφωνήσουν εκ κατασκευής
  • AEO layer: llms.txt και robots.txt είναι build-time endpoints που παράγονται από το site.config.ts, τα i18n λεξικά και το collection σε κάθε build, συν committed markdown mirror (22 index.md δίδυμα + llms-full.txt) με <link rel="alternate" type="text/markdown"> σε κάθε route
  • JSON-LD ProfessionalService: ΓΕΜΗ identifier, ΑΦΜ/VAT ID, localized areaServed (πόλη + χώρα), split ωράρια (πρωί + απόγευμα), και disambiguatingDescription που δηλώνει ρητά ότι η επιχείρηση δεν είναι δημόσια υπηρεσία
  • Deploy σε δύο hosts από ένα build shape: production σε Cloudflare Workers Static Assets μέσω wrangler, staging σε Docker (node:22-alpine → nginx:alpine) στο Coolify, με SITE_URL και STAGING_NOINDEX ως build args ώστε το staging να μη διεκδικεί ποτέ τα canonicals του production

Η συνεισφορά μου

Παρέδωσα το site end-to-end ως ο μοναδικός developer: από το design μέχρι το DNS. Πέρα από τις σελίδες, το μεγαλύτερο μέρος της δουλειάς ήταν τα gates που κάνουν το site συντηρήσιμο: hardened placeholder gate που σκανάρει 7 αρχεία για scaffold strings που ξέφυγαν (με comment-skipping και emptyValue sentinels ώστε πραγματικές τιμές να μην σκάνε false positives), linkinator crawl ολόκληρου του built output, τέσσερις Playwright σουίτες (smoke, i18n, lead form, responsive) και ένα WCAG AA contrast audit script που μετράει τις rendered σελίδες. Ανασχεδίασα επίσης το λογότυπο ψηφιακά, και έγραψα τα per-route OG cards που κάνουν render μέσω Playwright για τα πέντε top-level routes σε δύο γλώσσες.

Stack λεπτομέρειες

Η πιο ενδιαφέρουσα απόφαση είναι ότι το llms.txt δεν είναι στατικό αρχείο αλλά build-time endpoint: παράγεται από τα ίδια inputs που παράγουν και το site (config, λεξικά, collection), οπότε δεν μπορεί να ξεμείνει πίσω, και ένα σπασμένο input ρίχνει το build αντί να σερβίρει σιωπηλά λάθος κατάλογο. Το GA4 φορτώνει μόνο πίσω από το analytics toggle του cookie consent. Το contact form περνάει από τη δική μου self-hosted υπηρεσία φορμών (rorsa-platform) με το public site key στο markup, χωρίς κανένα secret στο repo, και με το Cloudflare Turnstile token να επαληθεύεται server-side. Και το check:links gate σκανάρει ολόκληρο το crawled dist αντί μόνο το homepage, με υπολογιζόμενη skip list αντί για config αρχείο που μπορεί να σκιαστεί σιωπηλά.

Αποτελέσματα

  • Live στο esupport-ioannina.gr, σε production στο Cloudflare Workers Static Assets
  • Δίγλωσσο: πλήρης αγγλικός καθρέφτης κάτω από /en/ με δομικά εγγυημένη i18n parity
  • Πλήρες CI: types, unit tests, placeholder gate, build, dead-link crawl, τέσσερις e2e σουίτες

Πρόκληση

Η πιο ενδιαφέρουσα σχεδιαστική πρόκληση ήταν το AEO layer. Ένα llms.txt που γράφεται με το χέρι είναι σωστό τη μέρα που γράφεται και ύποπτο κάθε επόμενη. Η λύση ήταν να γίνει ο κατάλογος παράγωγο του site αντί για περιγραφή του: το buildLlmsTxt() διαβάζει το site.config.ts, τα i18n λεξικά και το services collection μέσω κανονικών ES imports (κανένα readFileSync, ώστε ο builder να μένει environment-agnostic), και τρέχει σε κάθε build. Το ίδιο ισχύει και για το ποιες υπηρεσίες διαφημίζονται ως αναλυτικοί οδηγοί: το body-presence test που αποφασίζει αν μια κατηγορία παίρνει detail page είναι το ίδιο test που χτίζει τον κατάλογο του llms.txt και τα links της λίστας. Όταν η αλήθεια ορίζεται σε ένα σημείο, τρεις επιφάνειες δεν χρειάζονται συγχρονισμό, και ένα λάθος γίνεται build failure αντί για σιωπηλό drift.

Σύνδεσμοι

Παρόμοια έργα

  • Rorsa Tools: το MCP toolkit πίσω από το markdown mirror αυτού του site
  • Κόμβος Υγείας & Dr Christos Kolettis: άλλα Astro sites της ίδιας οικογένειας, χωρίς το AEO layer
Επικοινωνία

Ρυθμίσεις εμφάνισης

Κείμενο

Μέγεθος κειμένου
Απόσταση γραμμών
Απόσταση γραμμάτων

Χρώμα

Αντίθεση

Κίνηση και εικόνες

Ανάγνωση

Βοήθημα ανάγνωσης
Καμία αλλαγή