
STYLMET — Geländerzentrum
Die Website einer Werkstatt für Edelstahlgeländer und eines Geräteverleihs in Gorzów Wielkopolski, Polen.
Live und gepflegt — stylmet.pl ·
- React
- TypeScript
- Vite
- SSG
- PHP
- Local SEO
Eine schnelle, vorgerenderte Local-SEO-Website: Angebot, 132 Projekte, Verleih und Angebote.
Screenshots
Ziehen Sie die Leiste zur Seite, um zu blättern. Klicken Sie auf einen Screenshot, um ihn zu vergrößern, und wechseln Sie dann mit den Pfeiltasten.Vollständige Beschreibung
Die neue Website von Centrum Balustrad STYLMET — einer Werkstatt in Gorzów Wielkopolski, die seit 1993 Geländer, Handläufe, Zäune und Konstruktionen aus Edelstahl fertigt und nebenbei Anhänger, einen Autotransporter, eine Arbeitsbühne und ein Gerüst vermietet. Sie ersetzte eine Seite auf WordPress mit Elementor.
EINE SEITE, DIE DAS TELEFON KLINGELN LÄSST
Besucher kommen über die lokale Suche und das Google-Unternehmensprofil, meist mit dem Smartphone. Deshalb steht die Telefonnummer auf jeder Seite und bei jeder Bildschirmbreite im Header, und das Angebotsformular ist von überall einen Klick entfernt.
FERTIGES HTML FÜR JEDE SEITE
Jede Seite wird beim Build als statisches HTML erzeugt — mit vollständigem Text, strukturierten Daten und Sitemap. Suchmaschinen müssen kein JavaScript ausführen, und die Seite erscheint auch bei schwachem Empfang schnell.
132 PROJEKTE MIT WASSERZEICHEN
Eine Galerie mit Filtern und Vollbildansicht. Die Fotos des Kunden laufen durch ein eigenes Skript, das sie in mehrere Größen skaliert und das Firmenlogo aufbringt.
GERÄTEVERLEIH MIT PREISEN
Jeder Artikel hat technische Daten, den nötigen Führerschein, Netto- und Bruttopreise und eine eigene Galerie. Die Preise stehen auch in den strukturierten Daten, damit Google sie in den Ergebnissen zeigen kann.
ANGEBOTE MIT FOTOS
Das Formular nimmt bis zu drei Fotos oder PDFs an und schickt sie per E-Mail direkt ins Büro — ohne reCAPTCHA und ohne Cookie-Banner.
ZWEI DESIGNS
Dunkel, über Stahlprofilen, und hell, über gebürstetem Blech. Beide eigens gestaltet, nicht voneinander invertiert.
Technologie
Leistungsumfang: Umgesetzt bei GriffSoftware — Frontend-Implementierung, Prerendering, PHP-Formular-Endpoint, Bildverarbeitung, Deployment
- React 19
- TypeScript 6
- Vite 8
- Plain CSS + design tokens (dark / light)
- Hand-rolled router (~110 lines)
- PHP 8.1 enquiry endpoint (mail(), multipart attachments)
- Google Places API (live reviews)
- ESLint 10 + typescript-eslint
- tsc -b
- Prerender as a build gate
- Manual checks at 320–2560 px
- SSG prerender (renderToString) + hydration
- sharp image pipeline with watermarks
- JSON-LD, sitemap.xml, robots.txt
- Apache / LiteSpeed (.htaccess), dhosting.pl
Fallstudie — Architektur und Technologie
8 Min.
Das Projekt in einem Absatz
STYLMET ist ein Familienbetrieb in Gorzów Wielkopolski, der seit über dreißig Jahren Geländer, Handläufe, Rampen und Zäune aus Edelstahl fertigt und nebenbei einen kleinen Geräteverleih betreibt: zwei Anhänger, einen Autotransporter, eine 17,1-m-Arbeitsbühne und ein Gerüst. Die alte Seite lief auf WordPress mit Elementor. Das Briefing war kurz: Die Seite soll lokale Anfragen bringen — Anrufe und Angebotsanfragen — von Privatkunden, Wohnungseigentümergemeinschaften, Bauunternehmen und Behörden im Umkreis von etwa 40 km um Gorzów. Alles, was folgt, ergibt sich aus diesem einen Satz. Vom ersten Commit bis zum Launch vergingen keine zwei Wochen, mit mehreren Feedbackrunden des Kunden dazwischen.
Zwei Laufzeitabhängigkeiten
In dependencies stehen genau zwei Pakete: react und react-dom. Es gibt keinen React Router — sondern einen eigenen Router mit rund 110 Zeilen, der echte <a href>-Elemente rendert und nur einen normalen Linksklick abfängt, sodass Links ohne JavaScript funktionieren und crawlbar sind. Es gibt keine Formular- oder Validierungsbibliothek, keinen globalen State und keinen HTTP-Client — die ganze App hat ein einziges fetch. Phosphor-Icons werden von einem manuell gestarteten Skript als SVG-Pfade eingebettet, also landet keine Icon-Bibliothek im Bundle. Das ist kein Minimalismus um seiner selbst willen. Bei sechs Seiten, die sich ein paar Mal im Jahr ändern, ist jede Abhängigkeit ein paar Kilobyte mehr auf dem Smartphone des Kunden und eine Sache mehr, die man aktuell halten muss.
Prerendering statt SPA
npm run build erledigt vier Schritte nacheinander: Typprüfung, das Browser-Bundle, ein zweites SSR-Bundle für Node und schließlich prerender.mjs. Das Skript ruft render(path) für jede Route aus siteConfig.routes auf, setzt das Ergebnis in das Template ein und schreibt pro Seite eine eigene index.html, danach sitemap.xml und robots.txt. Im Browser übernimmt hydrateRoot das fertige HTML.
Jede Seite liegt als vollständiges Dokument auf dem Server — mit <title>, Beschreibung, Canonical, Open Graph und einem einzigen JSON-LD-Graphen (LocalBusiness, WebSite, WebPage, BreadcrumbList und auf der Verleihseite zusätzlich ein OfferCatalog mit Gerätepreisen). Eine Sache habe ich bewusst weggelassen: aggregateRating. Die Firma hat 5,0 bei Google und die Seite zitiert das, aber Bewertungen von einer anderen Plattform als strukturierte Daten auszuzeichnen, ist ein schneller Weg zu einer manuellen Maßnahme von Google. Die Begründung steht als Kommentar oben in der Datei, damit das später niemand in guter Absicht „repariert“.
Die alte Seite hatte sechs Adressen in der Sitemap. Vier gibt es unter denselben Namen, und /galeria/ sowie /ogrodzenia-gorzow/ bekommen 301-Weiterleitungen in der .htaccess — über Jahre aufgebaute Rankings verschwinden nicht zusammen mit WordPress.
Ein Design, das vor React feststeht
Das Design entstand dunkel — alle Artboards waren dunkel — und das helle Design ist seine eigens gestaltete Tagesversion, keine Invertierung. Alle Farben, Schriften, Abstände und Schatten stehen in einer einzigen Datei, colors.ts, die Custom Properties für beide Designs erzeugt. In den Stylesheets gibt es kein einziges Farbliteral. Der Trick dahinter: Die Stufen der Skala bedeuten Abstand zum Hintergrund, nicht Helligkeit. 100 ist immer der höchste Kontrast, 900 am nächsten am Hintergrund, also ist color: var(--color-neutral-300) in beiden Designs Sekundärtext.
Ein kleines Skript in index.html legt das Design fest, bevor React lädt: Es liest localStorage und setzt data-theme auf <html>, sodass eine vorgerenderte Seite ab dem ersten Frame die richtigen Farben hat. React liest dieses Attribut über useSyncExternalStore. So landen keine Werte, die nur der Browser kennt, im erzeugten HTML, und die Hydration läuft nicht auseinander.
Der Seitenhintergrund ist ein Bild von Stahl: Profile im dunklen Design, gebürstetes Blech im hellen. Das Blech zeichnet ein eigenes Skript, weil der Kunde das vorherige Foto mit Schleifspuren schlicht nicht als Metall erkannt hat. Die Deckkraft des Hintergrunds ist so gewählt, dass Sekundärtext selbst über dem hellsten Pixel den nötigen Kontrast behält. Ändert sich die Datei, muss neu gemessen werden.
132 Fotos und ein Wasserzeichen, das den Zuschnitt überlebt
Der Kunde lieferte Dutzende, dann über hundert Projektfotos und wünschte sich ein Wasserzeichen. Das Skript process-images.mjs (sharp, manuell gestartet, nicht Teil des Builds) liest ein Manifest mit Slugs, Kategorien und alt-Texten, skaliert jedes Foto in mehrere WebP-Varianten und brennt das Logo in die rechte untere Ecke ein.
Ein Problem zeigte sich sofort: Die Galerie zeigt Fotos vollständig, aber Angebotskacheln und die Startseite beschneiden sie mit object-fit: cover — und die eingebrannte Ecke verschwand einfach. Die Lösung sind zwei Wege, die sich nie vermischen dürfen. Die Galerie bekommt Dateien mit eingebranntem Zeichen. Jeder beschnittene Rahmen bekommt eine saubere Kopie aus dem Ordner kadr/ und die Klasse .watermarked, die dasselbe Zeichen per CSS in die Ecke des Rahmens zeichnet. Saubere Kopien entstehen nur für Fotos, die im Code tatsächlich verwendet werden, sodass der gesamte Bestand nie ohne Zeichen veröffentlicht wird. Die Größe des Zeichens ist ein calc() über die Maße der Lightbox, damit es auf jedem Bildschirm gleich wirkt. Ein zentriertes Zeichen und ein Zuschnitt zur Ecke hin wurden beide ausprobiert und vom Kunden abgelehnt.
Ein zweites, weniger offensichtliches Detail: Die .htaccess cacht .webp-Dateien ein Jahr lang als immutable, und ein neu markiertes Foto behält seinen Namen. Deshalb läuft jeder Bildpfad durch photoSrc(), das ?v= mit einem Hash über den gesamten Bestand anhängt. Ohne das sähen wiederkehrende Besucher ein Jahr lang die alten Pixel.
Ein Angebotsformular ohne reCAPTCHA und ohne Cookie-Banner
Serverseitig gibt es genau eine Datei: wyslij-zapytanie.php. Der Browser sendet multipart/form-data — die Felder als JSON in einem Teil (PHP verstümmelt Leerzeichen in Feldnamen) und bis zu drei Anhänge. Der Endpoint übernimmt alles, was man dem Browser nicht anvertrauen kann: Er prüft Origin und Größenlimits, erkennt den Dateityp an den ersten Bytes statt am Namen oder Content-Type, passt die Endung an, begrenzt auf fünf Anfragen pro Stunde je IP und entfernt CR/LF aus jedem Wert, der in einen Mail-Header gelangt — der klassische Weg, jemandem ein Bcc: unterzuschieben.
Vor Spam schützt ein Honeypot, kein reCAPTCHA. Das ist Absicht: Jedes Drittanbieter-Skript würde einen Cookie-Banner bedeuten, und ein Banner auf einer Seite, die zum Anrufen bewegen soll, ist reiner Verlust. Aus demselben Grund gibt es auf der Kontaktseite keine eingebettete Google-Karte, sondern einen von Hand gezeichneten SVG-Stadtplan mit Link zu Maps.
Ein Detail zeigte sich erst bei der DNS-Prüfung: Der SPF-Eintrag der Domain endet auf -all und erlaubt nur die Server von dhosting. Ein per mail() von einem anderen Hoster verschicktes Formular würde vorgeben, von stylmet.pl zu kommen, an SPF scheitern, und der Mailserver, auf dem das Empfängerpostfach liegt, dürfte es zu Recht ablehnen. Die Seite kam deshalb auf dasselbe Hosting wie die Mail, und diese Abhängigkeit ist in der Projektdokumentation festgehalten.
Ehrliche Meldungen sind hier eine Regel, keine Dekoration: Das Panel nach dem Absenden sagt genau, was passiert ist. Ist der Endpoint abgeschaltet, öffnet das Formular das Mailprogramm mit vorausgefüllter Nachricht und behauptet nie, etwas sei verschickt worden.
Google-Bewertungen, die SEO nicht schaden
Der Bewertungsbereich lädt echte Rezensionen über die Google Places API, aber erst nach der Hydration. Der erste Render — der im vorgerenderten HTML — zeigt immer die im Code hinterlegten Zitate, sodass Crawler Inhalt sehen und Server- und Client-Render übereinstimmen. Jeder Fehler (kein Schlüssel, Kontingent, Netzwerk) wird geloggt und zu null, und der Bereich bleibt bei den Zitaten. Der Ausfall eines Drittanbieters ist nicht das Problem des Besuchers.
Kundenfeedback, oder: Der Text muss stimmen
Mehrere Feedbackrunden änderten mehr als Farben. Der Schriftzug STYLMET wanderte unter das Zeichen, weil das Logo im Betrieb so aussieht, wodurch der Header von 78 auf 118 px wuchs. Seine Höhe ist eine einzige Variable, die Leiste, Menü, Abstandhalter und scroll-padding-top lesen. Die mobile Navigation wurde vom horizontal scrollenden Streifen aus dem Mockup zu einem vollständigen Menü mit inert, Fokusführung und Scroll-Sperre. Der Bauzaunverleih verschwand, weil er nie gestartet ist. Und der Satz „für einen Tag, ein Wochenende oder die ganze Baustelle“ wurde umgeschrieben, weil das Gerüst mindestens sieben Tage gemietet wird. Solche kleinen Hinweise sind wichtig und werden nicht kleingeredet.
Was dieses Projekt tatsächlich gelehrt hat
Die wichtigste Lektion: Bei einer kleinen Firmenwebsite machen die Einschränkungen die Architektur. Kein Cookie-Banner schloss reCAPTCHA, die Karte und Analytics aus. SPF entschied über das Hosting. Ein Jahres-Cache erzwang die Versionierung der Fotos, und der Zuschnitt zwei Wege für das Wasserzeichen. Keine dieser Entscheidungen kam aus Technologietrends, jede aus einer konkreten Tatsache über diese Firma und ihre Kunden. Was ich beim nächsten Projekt sofort wiederholen würde: diese Fakten in einem Briefing im Repository festhalten, zusammen mit der Begründung für jedes „das fehlt mit Absicht“. So macht die nächste Änderungsrunde nicht die Entscheidungen rückgängig, die das meiste Nachdenken gekostet haben.
