Leistungen

Frontend-Architektur, Integrations-Engineering

Branche

B2B-Marktplatz

Jahr

2023-2024

Eine vollständige Django-Anwendung via iframe auf Partner-Seiten einbetten.

Der Marktplatz des Kunden war nicht nur eine eigenständige Plattform - er musste auf Partner-Websites leben. Immobilienportale, Finanzberater, Branchenverzeichnisse. Jeder wollte den vollständigen Listing-Flow auf seiner eigenen Domain eingebettet, im eigenen Branding gestaltet, ohne eine separate Codebasis pflegen zu müssen. Die Lösung: eine einzige Django-Anwendung, die erkennt, wenn sie in einem iframe läuft, und ihre gesamte Rendering-Pipeline entsprechend anpasst.

Das Problem mit iframes

iframes sind das älteste Einbettungsprimitiv des Webs - und eines der frustrierendsten. Wenn man eine vollständige Anwendung in einen iframe auf einer Drittanbieter-Seite lädt, beginnen die Probleme sofort: Der iframe hat eine feste Höhe, sodass Inhalte überlaufen oder doppelte Scrollbalken entstehen. Modale öffnen sich im Viewport des iframes, nicht der Elternseite, und werden daher abgeschnitten oder verdeckt. Sitzungscookies können durch Drittanbieter-Cookie-Richtlinien blockiert werden. Und es gibt keinen nativen Kommunikationskanal zwischen der eingebetteten App und der Hostseite.

Die konventionelle Antwort lautet: 'Bau einfach eine API und lass den Partner alles rendern.' Aber für einen komplexen Marktplatz mit mehrstufigen Workflows, servergerenderten Templates und Formularvalidierung war es keine Option, das Frontend auf jeder Partner-Seite neu aufzubauen. Die Anwendung musste ein Django-Monolith bleiben. Sie musste nur ihren Container kennen.

Die Embed-Erkennung und die Template-Umschaltlogik:

Python
def is_embedded_view(request):
    """Detect whether the current request is rendered inside an iframe."""
    if request.GET.get('embed') == 'true':
        request.session['is_embedded'] = True
    return request.session.get('is_embedded', False)


def get_base_template(request):
    """Switch template inheritance based on embed mode."""
    if is_embedded_view(request):
        return '_base_blank.html'   # stripped-down shell: content only
    return '_base.html'             # full chrome: nav, footer, branding


# In any view:
@xframe_options_exempt
def company_detail(request, slug):
    base = get_base_template(request)
    return render(request, 'company/detail.html', {
        'base_template': base,
        ...
    })

Die Dual-Mode-Architektur

Im Kern ist die Lösung ein einzelner Boolean: Wird dieser Request in einem iframe gerendert? Eine Hilfsfunktion namens is_embedded_view() beantwortet diese Frage, indem sie auf einen embed-Query-Parameter oder ein Session-Flag prüft. Wenn true, wechselt die gesamte Template-Vererbungskette von der vollständigen _base.html (mit Navigation, Footer, Branding) zur reduzierten _base_blank.html - einer schlanken Shell, die nur den Content-Bereich rendert.

Djangos @xframe_options_exempt Decorator wird auf jede einbettbare View angewendet. Ohne ihn würde der X-Frame-Options-Header des Browsers verhindern, dass der iframe überhaupt gerendert wird. Dies ist ein bewusstes, View-für-View Opt-in, kein globales Deaktivieren des Frame-Schutzes.

Das Nachrichten-Protokoll

Verfolge, wie Cross-Origin-Events zwischen der eingebetteten App und der Hostseite fließen.

Partner-Seite

Die Hostseite lädt die Django-Anwendung in einem iframe mit einem embed=true Parameter.

iframe

Django erkennt den Embedded-Modus und rendert _base_blank.html statt des vollständigen Chrome-Templates.

postMessage

Cross-Origin-Events (fullScreenOn, fullScreenOff, resize) überbrücken die iframe/Parent-Grenze.

Parent JS

Die Hostseite lauscht auf Messages, passt die iframe-Größe an und verwaltet Backdrop-Overlays.

Nahtlose UX

Der Nutzer erlebt eine einheitliche Oberfläche - Modale, Formulare und Navigation fühlen sich nativ auf der Partner-Seite an.

  1. 01Partner-SeiteDie Hostseite lädt die Django-Anwendung in einem iframe mit einem embed=true Parameter.
  2. 02iframeDjango erkennt den Embedded-Modus und rendert _base_blank.html statt des vollständigen Chrome-Templates.
  3. 03postMessageCross-Origin-Events (fullScreenOn, fullScreenOff, resize) überbrücken die iframe/Parent-Grenze.
  4. 04Parent JSDie Hostseite lauscht auf Messages, passt die iframe-Größe an und verwaltet Backdrop-Overlays.
  5. 05Nahtlose UXDer Nutzer erlebt eine einheitliche Oberfläche - Modale, Formulare und Navigation fühlen sich nativ auf der Partner-Seite an.

Cross-Origin-Kommunikation

Der schwierigere Teil ist nicht das Rendering - es ist die Kommunikation. Die eingebettete App und die Hostseite leben auf verschiedenen Origins. Sie können nicht auf das DOM des jeweils anderen zugreifen, keinen JavaScript-Scope teilen und keine Cookies des anderen lesen. Die einzige Brücke ist window.postMessage().

Wenn sich ein Modal in der eingebetteten App öffnet, sendet es ein fullScreenOn-Event an das Elternfenster. Der Parent reagiert, indem er den iframe auf Vollbild erweitert und einen Backdrop-Overlay hinzufügt. Beim Schließen des Modals verkleinert fullScreenOff den iframe wieder auf seine Content-Höhe. Die Höhe des Modals wird ebenfalls via postMessage übermittelt, damit der Parent die exakten iframe-Dimensionen setzen kann und das Doppel-Scrollbalken-Problem vermieden wird.

Der iframe kämpft nicht gegen die Hostseite - er kooperiert mit ihr.

Das postMessage-Protokoll für Modal-Lifecycle-Events:

JavaScript
// Inside the embedded Django app
function openModal(modalEl) {
  modalEl.classList.add('is-visible');

  // Tell the parent page: "I need fullscreen"
  const height = modalEl.offsetHeight;
  window.parent.postMessage({
    type: 'fullScreenOn',
    modalHeight: height
  }, '*');
}

function closeModal(modalEl) {
  modalEl.classList.remove('is-visible');

  // Tell the parent page: "Back to normal"
  window.parent.postMessage({
    type: 'fullScreenOff'
  }, '*');
}

// --- On the partner site: ---
window.addEventListener('message', (event) => {
  const iframe = document.getElementById('marketplace-embed');
  if (event.data.type === 'fullScreenOn') {
    iframe.style.height = event.data.modalHeight + 'px';
    iframe.classList.add('fullscreen-overlay');
  }
  if (event.data.type === 'fullScreenOff') {
    iframe.classList.remove('fullscreen-overlay');
    iframe.style.height = '';  // back to auto-resize
  }
});

Die harten Nüsse

Cookie-Blocking

Safari und Firefox blockieren Drittanbieter-Cookies standardmäßig. Ein iframe auf partnerseite.de, der app.marktplatz.de lädt, kann keine Cookies für marktplatz.de setzen oder lesen. Das bricht Django-Sessions komplett. Der Workaround: Session-State über URL-Parameter beim initialen Embed-Load übergeben, dann localStorage als Session-Key-Fallback im iframe-Kontext verwenden.

Höhen-Sync

Der iframe-Inhalt ändert ständig seine Höhe - Akkordeons öffnen, Suchergebnisse laden, durch Listings blättern. Ein MutationObserver beobachtet den Document Body auf Größenänderungen und drosselt Resize-Messages an den Parent alle 100ms. Der Parent wendet die Höhe mit einer sanften CSS-Transition an, um visuelles Ruckeln zu vermeiden.

Modal-Z-Index-Handling

Ein Modal innerhalb eines iframes kann visuell nie die Grenzen des iframes verlassen. Der Workaround: Wenn sich ein Modal öffnet, wird der iframe selbst im Parent-Fenster auf Vollbild gesetzt. Der Parent erstellt seinen eigenen Backdrop-Overlay, und der iframe füllt den Viewport aus. Aus Sicht des Nutzers sieht es wie ein natives Modal aus. Hinter den Kulissen ist es ein koordinierter postMessage-Austausch.

12+Partner-Seiten
99,9%Embed-Uptime
0<2KBJS-Overhead
WAS DER KUNDE SAGT

"Unsere Partner können die vollständige Marktplatz-Erfahrung auf ihren eigenen Seiten in unter einer Stunde integrieren. Keine API-Integration, keine Frontend-Entwicklung - nur ein iframe-Tag und ein Script-Snippet."

Product Owner

B2B-Marktplatz · Plattform-Integrationen

FAQ

Warum nicht ein separates einbettbares Frontend bauen?

Wie gehst du mit CSP und X-Frame-Options um?

Funktioniert die Einbettung auf Mobilgeräten?

TECHNOLOGIE-STACK