Software & Praxis

Eine übersichtliche PHP-Anwendung strukturieren

Drei überschaubare Dateien machen Verantwortlichkeiten sichtbar. Ein Leitfaden für klare Grenzen, sichere Textausgabe und nachvollziehbare Änderungen.

KI-generiertes Symbolmotiv: Drei aufgestellte Module Einstieg, Zuordnung und Ausgabe trennen die Rollen einer PHP-Anwendung. Rote Datenverbindungen folgen der AZ-Herobildwelt.

Eine PHP-Anwendung kann mit einer einzigen Datei beginnen. Solange sie nur wenige feste Inhalte ausgibt, ist das leicht nachvollziehbar. Schwieriger wird es, wenn Routing, Datenbankzugriff, Eingabeprüfung, Mailversand und HTML ineinanderwachsen. Eine kleine Änderung kann dann unbeabsichtigt mehrere Abläufe beeinflussen.

Dieser Beitrag entwickelt eine begrenzte Beispielanwendung mit zwei öffentlichen Informationsseiten. Sie benötigt keine Datenbank und keine zusätzliche Bibliothek. Das Beispiel ist eine Lerngrundlage und kein vollständiges produktives System. Sein Zweck ist, Verantwortlichkeiten sichtbar zu machen und die Folgen kleiner Architekturentscheidungen praktisch zu prüfen.

1. Nach Verantwortlichkeiten statt nach Modebegriffen aufteilen

Eine gute Struktur beantwortet einfache Fragen. Wo wird eine Adresse ausgewertet? Wo entsteht die fachliche Entscheidung? Wo wird HTML erzeugt? Und welche Dateien darf ein Browser überhaupt anfordern? Wenn diese Fragen nur durch das Lesen einer sehr großen Datei beantwortet werden können, ist eine gezielte Aufteilung sinnvoll.

Die Ordnernamen sind dabei weniger wichtig als ihre Bedeutung. Ein Verzeichnis namens „Services“ macht eine Anwendung nicht automatisch gut strukturiert. Es muss erkennbar sein, welche Aufgaben darin liegen und welche Abhängigkeiten erlaubt sind. Ebenso benötigt nicht jedes kleine Projekt sofort eine umfangreiche Schichtenarchitektur.

Für unser Beispiel genügen ein öffentlicher Einstieg und zwei interne Dateien. Eine Datei ordnet bekannte Adressen den Inhalten zu. Eine andere erzeugt die kleine HTML-Seite. So bleibt die Anfrageverarbeitung von der Darstellung getrennt, ohne die einfache Aufgabe hinter vielen Abstraktionen zu verstecken.

VerantwortungIm BeispielBewusst ausgeschlossen
Anfrage entgegennehmenpublic/index.phpFachliche Datenbankentscheidungen
Ziel bestimmenapp/routes.phpBeliebige Dateien aus URL-Werten laden
Antwort darstellenapp/render.phpVerdeckte Geschäftsaktionen
Entwicklungsnachweise aufbewahrentests und ProjektunterlagenÖffentliche Auslieferung interner Daten

2. Die öffentliche Webwurzel bewusst begrenzen

Der Webserver sollte nur das Verzeichnis veröffentlichen, dessen Dateien tatsächlich öffentlich erreichbar sein sollen. Im Beispiel ist das public. Interner PHP-Code liegt daneben unter app. Ein Dateiname wie „private-config.php“ schützt eine Datei nicht allein durch seine Bezeichnung.

projekt/
├── app/
│   ├── render.php
│   └── routes.php
├── public/
│   └── index.php
└── tests/

Die Trennung muss mit der tatsächlichen Hostingkonfiguration übereinstimmen. Wenn der Server stattdessen das gesamte Projektverzeichnis ausliefert, ist die gedachte Grenze nicht umgesetzt. Prüfen Sie deshalb nach der Einrichtung, ob eine interne Datei über eine direkte Adresse erreichbar ist. Erwartet wird keine öffentliche Ausgabe dieser Datei.

Auch Sicherungen, Protokolle und Zugangsdaten werden nicht als normale öffentliche Dateien abgelegt. Das ist eine organisatorische und technische Aufgabe zugleich. Ein späteres Kopierskript darf nicht versehentlich den gesamten Arbeitsordner veröffentlichen. Der freizugebende Dateiumfang sollte ausdrücklich definiert sein.

Drei geprüfte PHP-Dateien: public/index.php prüft die Anfrage, app/routes.php liefert feste Seitendaten und app/render.php maskiert die HTML-Textausgabe. Ergänzende tatsächliche Tests zeigen 404, HEAD ohne Körper und 405 mit Allow.
Die Rollen und HTTP-Ergebnisse stammen aus dem ausgeführten Artikelbeispiel. Nur public/ wird als Webwurzel bereitgestellt. Die Maskierung ist auf die gezeigten HTML-Textstellen bezogen.

3. Eine kleine Ausgabe mit klarer Eingabe schreiben

Die Datei app/render.php erhält zwei Texte und erzeugt daraus ein vollständiges HTML-Dokument. Beide Werte werden für HTML-Text beziehungsweise ein HTML-Attribut passend maskiert. Im Beispiel werden keine frei übergebenen HTML-Blöcke verarbeitet. So bleibt die Grenze leicht verständlich.

<?php
declare(strict_types=1);

function renderPage(string $title, string $intro): string
{
    $escape = static fn(string $value): string => htmlspecialchars(
        $value,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    return '<!doctype html><html lang="de"><head>'
        . '<meta charset="utf-8">'
        . '<meta name="viewport" content="width=device-width, initial-scale=1">'
        . '<title>' . $escape($title) . '</title>'
        . '</head><body><main>'
        . '<h1>' . $escape($title) . '</h1>'
        . '<p>' . $escape($intro) . '</p>'
        . '</main></body></html>';
}

Die PHP-Dokumentation zu htmlspecialchars erklärt die Umwandlung besonderer Zeichen. Die Funktion ist kein universeller Schutz für jeden Ausgabekontext. JavaScript, URLs und andere Zusammenhänge können andere Regeln benötigen. Hier ist der Anwendungsfall bewusst auf gewöhnlichen HTML-Text begrenzt.

Für größere Ansichten ist eine eigene Vorlagendatei häufig angenehmer als lange verkettete Zeichenfolgen. Die Verantwortung bleibt dieselbe: Eine Vorlage erhält ausdrücklich benannte Daten und löst keine versteckten Geschäftsaktionen aus. Ob ein Template-System sinnvoll ist, folgt dem tatsächlichen Umfang und der vorhandenen Projektbasis.

4. Bekannte Adressen auf definierte Ergebnisse abbilden

Die zweite interne Datei ordnet einer bekannten Adresse eine kleine Ergebnisstruktur zu. Eine unbekannte Adresse liefert einen echten Nicht-gefunden-Status. Es wird kein Dateiname aus der Adresse gebildet. Dadurch bleibt der Übergang von einer öffentlichen Anfrage zu internem Code begrenzt.

<?php
declare(strict_types=1);

function resolvePage(string $path): array
{
    return match ($path) {
        '/' => [
            'status' => 200,
            'title' => 'Ein klarer Einstieg',
            'intro' => 'Diese Lernseite erklärt eine kleine PHP-Struktur.'
        ],
        '/leistungen' => [
            'status' => 200,
            'title' => 'Leistungen im Überblick',
            'intro' => 'Hier stehen ausschließlich feste Beispieldaten.'
        ],
        default => [
            'status' => 404,
            'title' => 'Seite nicht gefunden',
            'intro' => 'Die angeforderte Adresse ist nicht vorhanden.'
        ]
    };
}

Diese Aufteilung ist für die kleine Demonstration ausreichend. Eine umfangreiche Anwendung kann stattdessen einen Router und Controller verwenden. Entscheidend ist die kontrollierte Zuordnung. Eine Anfrage darf nicht durch unkontrollierte Pfadverkettung bestimmen, welche interne PHP-Datei eingebunden wird.

Die Ergebnisstruktur enthält Status, Titel und Einleitung. Eine spätere Erweiterung sollte diese Vereinbarung bewusst verändern. Wenn einzelne Routen plötzlich zusätzliche versteckte Schlüssel benötigen, muss das in der Schnittstelle nachvollziehbar werden. Andernfalls wird die vermeintlich einfache Struktur nur an eine andere Stelle verschoben.

5. Den öffentlichen Einstieg kurz halten

In public/index.php laufen die wenigen Schritte zusammen. Interne Dateien werden über feste, vom Quellcode vorgegebene Pfade geladen. Danach wird die Methode geprüft, der Pfad bestimmt und eine Antwort erzeugt. Die Darstellung wird erst ausgegeben, nachdem Status und Inhaltstyp festgelegt sind.

<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/app/render.php';
require_once dirname(__DIR__) . '/app/routes.php';

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
header('Content-Type: text/html; charset=UTF-8');

if (!in_array($method, ['GET', 'HEAD'], true)) {
    header('Allow: GET, HEAD');
    http_response_code(405);
    echo renderPage('Methode nicht erlaubt', 'Diese Lernseite ist nur lesend.');
    exit;
}

$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
if (!is_string($path)) {
    http_response_code(400);
    if ($method !== 'HEAD') echo renderPage('Ungültige Anfrage', 'Die Adresse ist ungültig.');
    exit;
}

$page = resolvePage($path);
http_response_code($page['status']);
if ($method !== 'HEAD') echo renderPage($page['title'], $page['intro']);

require_once bindet eine erforderliche Datei nicht erneut ein, wenn sie bereits geladen wurde. In der Demonstration ist der Ladeweg damit ausdrücklich erkennbar. Ein größeres Projekt kann Autoloading verwenden. Die Entscheidung ändert nicht die Notwendigkeit, interne Dateipfade von unkontrollierten Anfragewerten zu trennen.

Der Code verarbeitet keine Formulare. Ein späterer Schreibvorgang braucht zusätzliche Regeln, beispielsweise Authentisierung, Berechtigungsprüfung und Schutz vor ungewollten Aktionen. Die hier angebotenen Methoden sind bewusst auf Lesen begrenzt. Eine bloße Änderung von GET zu POST implementiert noch keinen sicheren Geschäftsprozess.

6. Konfiguration von fachlichen Daten unterscheiden

Konfiguration beschreibt, wie eine Anwendung betrieben wird. Dazu können Datenbankverbindungen, Versandwege und erlaubte öffentliche Adressen gehören. Fachliche Daten sind dagegen beispielsweise Kundenaufträge oder Kursangebote. Beide Bereiche benötigen unterschiedliche Pflege und dürfen nicht zufällig in derselben Datei vermischt werden.

Für die kleine Demonstration ist keine geheime Konfiguration nötig. In einem realen Projekt wird ihr Speicherort bewusst festgelegt. Zugangsdaten gehören nicht in eine öffentliche JavaScript-Datei oder in ein transportables Beispielpaket. Auch ausführliche Fehlermeldungen dürfen sie nicht versehentlich ausgeben.

Unterschiedliche Umgebungen sollten ihre Werte ausdrücklich beziehen. Eine Entwicklungsumgebung kann Nachrichten in Dateien schreiben, während die produktive Umgebung einen echten Versandweg verwendet. Eine Kopie der Konfiguration darf diese Unterschiede nicht ungeprüft überschreiben. Das ist insbesondere bei Veröffentlichungen und Wiederherstellungen wichtig.

Dokumentieren Sie die erforderlichen Namen und Bedeutungen ohne geheime Werte. Ein neuer Bearbeiter soll erkennen, welche Angaben nötig sind und wer sie verantwortet. Eine Beispielkonfiguration kann Platzhalter enthalten, aber keine erfundenen echten Zugangsdaten. Fehlende notwendige Werte sollten kontrolliert erkannt werden.

7. Fachliche Regeln aus der Ausgabe heraushalten

Sobald Daten hinzukommen, wächst die Versuchung, direkt innerhalb einer HTML-Vorlage Abfragen und Entscheidungen auszuführen. Eine kleine Formatierung ist dort angemessen. Die Entscheidung, ob ein Auftrag angenommen werden darf oder eine Zahlung ausgelöst wird, benötigt dagegen eine eigene fachliche Verantwortung.

Ein sinnvoller Zwischenstand kann sehr einfach sein: Eine Funktion erhält geprüfte Eingaben und liefert ein Ergebnis. Erst danach entscheidet die Ausgabe, wie dieses Ergebnis dargestellt wird. So kann dieselbe Regel später auch in einem Hintergrundauftrag oder einer Schnittstelle verwendet werden, ohne HTML nachzubauen.

Vermeiden Sie gleichzeitig eine übertriebene Zerlegung. Eine Klasse mit nur einem bedeutungslosen Durchreichaufruf schafft nicht automatisch Klarheit. Jede zusätzliche Grenze soll eine konkrete Veränderung oder Prüfung erleichtern. Wenn niemand ihren Zweck erklären kann, lohnt sich eine Vereinfachung.

Beim Review hilft die Frage: Würde eine Änderung der Darstellung die fachliche Entscheidung beeinflussen? Falls ja, sind möglicherweise Verantwortlichkeiten vermischt. Umgekehrt sollte eine neue Geschäftsregel nicht erfordern, dieselbe Bedingung in mehreren Vorlagen und Schnittstellen unterschiedlich zu ergänzen.

8. Fehlerfälle als Teil der Struktur behandeln

Eine unbekannte Adresse ist ein erwarteter Zustand und wird im Beispiel mit 404 beantwortet. Eine nicht erlaubte Methode erhält 405. Eine ausgefallene Datenbank wäre dagegen ein Betriebsfehler. Diese Unterschiede gehören sowohl in die technische Antwort als auch in die verständliche Rückmeldung.

Interne Details werden nicht ungeprüft öffentlich ausgegeben. Ein Besucher benötigt einen zutreffenden Hinweis auf den fehlgeschlagenen Vorgang, aber keine Zugangsdaten oder vollständige Dateipfade. Die für die Diagnose erforderlichen Informationen werden im vorgesehenen Protokoll erfasst und angemessen geschützt.

Das Beispiel zeigt bewusst keinen umfassenden Fehlerbehandlungsrahmen. Für eine Anwendung mit externen Diensten muss festgelegt werden, wo Fehler aufgefangen, protokolliert und in eine Antwort übersetzt werden. Eine zentrale Grenze kann dabei helfen. Sie darf wichtige Zustände aber nicht durch eine immer gleiche vermeintliche Erfolgsmeldung verdecken.

Nach einer fehlgeschlagenen Schreibaktion ist außerdem zu prüfen, ob etwas teilweise gespeichert wurde. Transaktionen und idempotente Abläufe können dann erforderlich sein. Diese fachliche Zuverlässigkeit entsteht nicht allein durch eine neue Ordnerstruktur. Die Struktur soll nur ermöglichen, sie klar zu implementieren und zu prüfen.

9. Die Beispielanwendung unter passenden Bedingungen prüfen

Speichern Sie die drei Dateien entsprechend dem gezeigten Baum. Für eine kontrollierte Entwicklungsprüfung kann der integrierte PHP-Server verwendet werden. Starten Sie ihn aus dem Projektverzeichnis mit php -S 127.0.0.1:8080 -t public public/index.php. Der letzte Pfad sorgt im Beispiel dafür, dass die Anfragen den gemeinsamen Einstieg erreichen.

Die PHP-Dokumentation zum eingebauten Webserver beschreibt ihn als Entwicklungswerkzeug. Er ist keine Empfehlung für einen öffentlich erreichbaren Produktivbetrieb. Die spätere Hostingkonfiguration wird mit dem tatsächlich verwendeten Webserver eingerichtet und geprüft.

PrüfungErwartungWarum sie wichtig ist
GET /200 mit HauptüberschriftBekannte Startadresse funktioniert
GET /leistungen200 mit LeistungsüberschriftRouting und Ausgabe passen zusammen
GET /unbekannt404Fehlerseite behauptet keinen Erfolg
HEAD /200 ohne AntwortkörperMethode wird bewusst behandelt
POST /405Nicht angebotene Aktion wird abgelehnt
Direkter Zugriff auf interne DateiKeine öffentliche QuelltextausgabeWebwurzel ist tatsächlich begrenzt

Prüfen Sie nicht nur den sichtbaren Text. Eine hübsche Fehlerseite mit Status 200 würde die Aussage der Antwort verändern. Ebenso ist eine erfolgreiche Syntaxprüfung kein Beleg, dass die Adresse auf dem tatsächlichen Server erreichbar ist. Unterschiedliche Prüfarten beantworten unterschiedliche Fragen.

10. Änderungen und Veröffentlichungen nachvollziehbar halten

Für jede Änderung sollte klar sein, welche Dateien und Daten betroffen sind. Ein kleiner Funktionsauftrag darf nicht unbemerkt eine ganze Konfiguration ersetzen. Vor einer Veröffentlichung werden vorhandener Stand, Sicherung und Rückweg geprüft. Anschließend wird der konkrete Nutzerablauf erneut getestet.

Versionsverwaltung hilft, Unterschiede zu erkennen. Sie ersetzt jedoch keine Sicherung laufender Daten und keine Prüfung des Zielsystems. Besonders bei Datenbankänderungen muss bekannt sein, ob inzwischen neue Eingaben entstanden sind. Eine alte Gesamtsicherung darf diese nicht ungeprüft überschreiben.

Trennen Sie wiederverwendbare technische Bausteine von projektspezifischen Inhalten. Ein allgemeiner Fehler kann dann gezielt in einer gemeinsamen Grundlage behoben werden. Die individuelle Gestaltung und die fachlichen Daten eines anderen Kunden werden nicht als beiläufige Kopie übernommen. Klare Grenzen erleichtern diese Entscheidung.

11. Abhängigkeiten in eine nachvollziehbare Richtung führen

In der kleinen Anwendung kennt der Einstieg die Routingfunktion und die Ausgabe. Die Ausgabe kennt dagegen weder die aktuelle Anfrage noch die Routingtabelle. Diese Richtung macht einen Unterschied: Die Ausgabefunktion kann mit beliebigen passenden Textwerten geprüft werden, ohne dafür eine HTTP-Anfrage nachzubauen.

Würde die Vorlage selbst auf globale Anfragewerte zugreifen, bliebe diese Abhängigkeit im Funktionsaufruf verborgen. Ein Test könnte scheinbar zwei Texte übergeben und dennoch einen anderen Inhalt erhalten, weil zusätzlich ein Parameter in der Umgebung wirkt. Solche versteckten Einflüsse erschweren sowohl Fehlersuche als auch Wiederverwendung.

Das bedeutet nicht, dass jede Abhängigkeit sofort ein eigenes Interface benötigt. Für eine feste kleine Funktion kann ein eindeutiges Argument genügen. Bei einem externen Versanddienst oder einem Speicherzugriff kann eine austauschbare Schnittstelle dagegen helfen, Fehlerfälle ohne tatsächlichen Versand zu prüfen. Die zusätzliche Struktur hat dann einen konkreten Zweck.

Zeichnen Sie für ein gewachsenes Projekt eine kleine Abhängigkeitsübersicht. Welche Datei lädt welche andere Datei? Wo werden Verbindungen geöffnet? Welche Funktion versendet tatsächlich eine Nachricht? Ein solcher Überblick zeigt oft, dass eine scheinbare Darstellungshilfe unerwartet einen Geschäftsprozess startet. Diese Vermischung sollte vor einer weiteren Wiederverwendung geklärt werden.

12. Eine vorhandene Anwendung in kleinen Schritten ordnen

Eine bestehende Anwendung muss nicht pauschal neu geschrieben werden. Beginnen Sie mit einem abgegrenzten Ablauf und halten Sie sein bisheriges Verhalten fest. Dazu gehören erfolgreiche Eingaben, typische Fehler und die tatsächlich erzeugten Antworten. Ohne diese Grundlage kann eine aufgeräumte neue Struktur unbemerkt fachliche Eigenschaften verändern.

Ein erster Schritt kann sein, die Ausgabe einer einzelnen Seite in eine Funktion mit klaren Eingaben zu verschieben. Anschließend wird dieselbe Anfrage erneut geprüft. Erst wenn dieser Schritt funktioniert, folgt die nächste Verantwortung. So bleibt die Veränderung überprüfbar und ein Fehler lässt sich leichter einer konkreten Umstellung zuordnen.

Achten Sie besonders auf globale Variablen und Dateien mit Nebenwirkungen. Eine eingebundene Datei kann gleichzeitig Funktionen definieren und sofort Daten verändern. Vor der Trennung muss klar sein, ob diese Wirkung beabsichtigt ist. Ein zusätzlicher require-Aufruf darf nicht versehentlich eine Nachricht erneut versenden oder einen Auftrag doppelt anlegen.

Dokumentieren Sie bei jeder neuen Grenze die noch vorhandenen Annahmen. Vielleicht wird eine Datenbankverbindung vorerst gemeinsam genutzt oder eine alte Vorlage erwartet einen bestimmten Namen. Solche Übergänge dürfen intern beschrieben werden. Sie sind kein Grund für technische Baustellenhinweise auf der öffentlichen Seite.

Nach der fachlichen Prüfung kann die alte ungenutzte Variante kontrolliert entfernt werden. Zwei parallel gepflegte Wege mit derselben Aufgabe machen den nächsten Schritt häufig schwieriger. Der Rückweg gehört in eine Sicherung und in die Versionshistorie, statt als zufällig erreichbare zweite Anwendung im öffentlichen Verzeichnis zu bleiben.

13. Das Ausgabeverhalten mit problematischen Zeichen prüfen

Ein nützlicher Test für unsere Ausgabefunktion verwendet Zeichen, die in HTML eine besondere Bedeutung haben. Übergeben Sie als Titel beispielsweise <Beispiel & Prüfung>. Im gerenderten Dokument soll dieser Text sichtbar bleiben. Er darf nicht als neues HTML-Element behandelt werden.

Prüfen Sie beide Ausgabestellen: den Dokumenttitel und die Hauptüberschrift. Dass eine Stelle korrekt maskiert wurde, belegt nicht automatisch die zweite. Die zentrale Hilfsfunktion reduziert zwar Wiederholung, doch die Verwendung muss weiterhin stimmen. Eine versehentlich direkt angehängte Variable könnte die Vereinbarung umgehen.

Der Test ist bewusst eng gefasst. Er belegt die Textausgabe dieses Beispiels und keine vollständige Sicherheitsfreigabe. Werden später Attribute, Links oder Skriptdaten ergänzt, brauchen sie eigene Regeln und Prüffälle. Eine verlässliche Architektur hält diese Unterschiede sichtbar und vermeidet einen einzigen unklaren Helfer, der angeblich jeden Inhalt überall sicher macht.

14. Checkliste für eine übersichtliche PHP-Basis

  • Öffentlicher Einstieg und interne Dateien sind erkennbar getrennt.
  • Die tatsächliche Webwurzel entspricht dieser Grenze.
  • Adressen werden kontrolliert auf bekannte Ziele abgebildet.
  • Ausgabe erhält ausdrücklich benannte Daten.
  • Maskierung passt zum jeweiligen Ausgabekontext.
  • Konfiguration und geheime Werte werden geschützt verwaltet.
  • Fachliche Regeln sind nicht unbemerkt in Vorlagen verteilt.
  • Erwartete Fehler und Betriebsfehler werden unterschieden.
  • Statuscodes und vollständige Abläufe wurden tatsächlich geprüft.
  • Veröffentlichung und Rückweg erhalten vorhandene Inhalte und Daten.

Häufige Fragen

Braucht jedes Projekt ein Framework?

Nein. Ein Framework kann bei größerem Umfang, vorhandener Teamerfahrung oder passenden Anforderungen sinnvoll sein. Die Wahl folgt der Aufgabe und der langfristigen Pflege.

Wie viele Dateien sind richtig?

Eine feste Anzahl gibt es nicht. Jede Aufteilung sollte eine verständliche Verantwortung besitzen und Änderungen oder Prüfungen erleichtern. Viele kleine Dateien können ebenso unübersichtlich sein wie eine einzige große.

Reicht die gezeigte Anwendung für einen produktiven Kundenbereich?

Nein. Dafür fehlen unter anderem Anmeldung, Berechtigungen und die fachlichen Funktionen. Das Beispiel erklärt eine begrenzte lesende Grundstruktur.

Quellen und passende Vertiefung

Die verlinkten offiziellen PHP-Handbuchseiten erläutern Datei-Einbindung, HTML-Maskierung und den Entwicklungsserver. Quellenprüfung am 9. November 2026. Ordnerstruktur und Code sind eigene Lernbeispiele.

Die Ausgabeseite sollte auf einer verständlichen Dokumentstruktur beruhen: Semantisches HTML anhand einer vollständigen Beispielseite. Für die Planung des tatsächlichen Angebots hilft Was eine professionelle Unternehmenswebseite leisten muss.

Gemeinsam weiterdenken

Was möchten Sie bewegen?

Eine Idee, eine konkrete Aufgabe oder der nächste Schritt mit KI. Wir hören zu.

Sprechen wir darüber