Im Shopify-Architektur-Ratgeber besprechen wir die Grundlagen. Heute eine praktische Frage, die in fast jedem Shopify-Projekt auftaucht: Sollte ich diese Feature im Theme (Liquid) oder in einer Custom App bauen?
Wir bei smplx. beantworten diese Frage nicht theoretisch. Wir schauen uns deinen Use-Case an, kalkulieren Kosten, Performance und Maintenance – und treffen dann eine fundierte Entscheidung.
Die Grundlegenden Unterschiede
Theme-Code (Liquid)
Was ist Liquid?
Liquid ist Shopify's Template-Language. Code im Theme läuft im Storefront – also im Browser des Kunden. Das macht es schnell für einfache Dinge, aber auch limitiert.
Architektur:
Kunde Browser → Liquid im Theme → Rendered HTML
Pros:
- Einfaches Deployment (Git Push, Theme-Editor)
- Sofort verfügbar nach dem Push
- Gute Performance für einfache Dinge (kein API-Call nötig)
- Nah am Katalog-Daten (Produkte, Collections, etc.)
- Keine zusätzliche Infrastruktur
Cons:
- Keine Logik auf dem Server (Security-Risk bei sensiblen Daten)
- Limits bei komplexer Geschäftslogik
- Begrenzte Debugging-Tools
- Kein Zugriff auf Admin API
- Schwer zu testen automatisiert
Custom App
Was ist eine Custom App?
Eine Custom App ist Code, der auf Shopify's Servern läuft (Backend) oder auf deinen eigenen Servern. Sie hat Zugriff auf die Admin API und kann direkt mit Shop-Daten arbeiten.
Architektur:
Kunde Browser → Shopify Admin / Deine App → Liquid im Theme (optional) → Rendered HTML
Pros:
- Zugriff auf Admin API (Schreib-Operationen!)
- Server-seitige Logik = sichere Verarbeitung
- Webhooks für Event-Driven Architecture
- Skalierbar für komplexe Szenarien
- Bessere Debugging-Tools
- Automatisiertes Testing möglich
Cons:
- Komplexerer Deployment-Prozess
- API-Rate-Limits beachten
- Mehr Infrastruktur (Server / Shopify Platform)
- Höhere Latenz für Echtzeit-Daten
- Kosten für Hosting/Infrastruktur
Real-World-Entscheidungen: Konkrete Beispiele
Beispiel 1: Dynamische Rabatt-Logik
Szenario: Du möchtest Kunden basierend auf Kauf-Historie automatisch Rabatte geben.
Option 1: Im Theme (Liquid)
{% if customer %}
{% if customer.orders_count > 10 %}
<p class="discount-banner">
VIP-Kunde! +10% auf alles!
</p>
{% endif %}
{% endif %}
Problem:
- Nur UI-Anzeige, nicht auf den Cart anwenden
- Kein Zugriff auf Order-Daten in Echtzeit
- Keine Automatisierung möglich
Option 2: Custom App mit Admin API
// Bei Product-Add-To-Cart (via Webhook)
const app = express();
app.post('/webhooks/orders/create', async (req, res) => {
const order = req.body.order;
const customer = order.customer;
if (customer.orders_count > 10) {
// Admin API: Automatisch Discount Code erstellen
await shopify.post('/admin/api/2024-01/discount_codes.json', {
discount_code: {
price_rule_id: VETERAN_RULE_ID,
code: `VIP_${customer.id}`,
}
});
}
res.status(200).send({ success: true });
});
Entscheidung: Custom App (Webhooks + Admin API) Grund: Automatische Discount-Anwendung braucht Server-Logik
Beispiel 2: Custom Checkout Extension
Szenario: Du möchtest einen Custom Feld im Checkout (z.B. "Lieferdatum-Wunsch").
Option 1: Theme Liquid + JavaScript
<input type="date" name="attributes[preferred_delivery_date]">
<script>
// Validation im Browser
document.querySelector('form').addEventListener('submit', (e) => {
const date = document.querySelector('input[name*="delivery_date"]').value;
if (new Date(date) < new Date()) {
e.preventDefault();
alert('Bitte Zukunftsdatum wählen');
}
});
</script>
Problem:
- Checkout ist zu sensitiv für nur Browser-Validation
- Keine Verknüpfung mit Fulfillment-Logik
- Keine Benachrichtigung ans Warehouse
Option 2: Checkout Extension (App-Block)
// Extension auf Checkout-UI (moderne Shopify-API)
import { useExtensionInput } from '@shopify/checkout-ui-extensions-react';
import { Box, Text, Select } from '@shopify/checkout-ui-extensions-react';
export default function DeliveryDateExtension() {
const { extensionPoint } = useExtensionInput();
return (
<Box border="base" padding="base">
<Text>Wunsch-Lieferdatum:</Text>
<Select
options={generateFutureDate()}
onChange={(value) => applyAttributes({ delivery_date: value })}
/>
</Box>
);
}
Entscheidung: Checkout Extension (moderne App-Architektur) Grund: Checkout ist Shopify Core – Extensions sind der richtige Ort
Beispiel 3: Bekateq Case Study
Bekateq hatte ursprünglich eine Shop mit 5+ Custom Apps für verschiedene Funktionen. Die Komplexität war nicht mehr zu handhaben.
Problem:
- Jede App brauchte Monitoring
- Unterschiedliche Deployments
- Rate Limiting war kritisch
- Wartungskosten explodierten
Lösung: Bekateq migrierte zu einer Single Custom App mit 4400+ Zeilen und Zero Dependencies (außer Shopify SDK).
Lernpunkt: Nicht jede kleine Funktion braucht eine separate App. Wenige, gut durchdachte Apps > viele kleine Apps.
Entscheidungsmatrix: Wann nutze ich was?
| Feature | Theme (Liquid) | Custom App | Checkout Extension |
|---|---|---|---|
| Produktseiten-UI | ✅ Ja | ❌ Nein | ❌ Nein |
| Collection-Filter | ✅ Ja | ⚠️ Optional | ❌ Nein |
| Cart-Logik | ⚠️ Nur View | ✅ Ja | ✅ Ja |
| Checkout-Feld | ❌ Unsicher | ⚠️ Möglich | ✅ Ja |
| Rabatt-Automation | ❌ Nein | ✅ Ja | ❌ Nein |
| Webhook-Verarbeitung | ❌ Nein | ✅ Ja | ❌ Nein |
| Admin Panel | ❌ Nein | ✅ Ja | ❌ Nein |
| Echtzeit-Sync mit externem System | ❌ Nein | ✅ Ja | ❌ Nein |
Performance-Vergleich
Szenario: Produkt wird zur Collection hinzugefügt
Theme-Only (Liquid-Loop):
Browser-Request → Server rendert → HTML mit Loop
Rendering-Zeit: ~200ms (abhängig von Collection-Größe)
Custom App (GraphQL Admin API):
Frontend-Request → Admin API Call → Backend-Logik → Webhook → Theme-Update
Latenz: ~500-1000ms (API Call + Webhook-Verarbeitung)
Best Practice:
- Zeitkritisch (< 500ms): Theme
- Nicht zeitkritisch (Automation, Background-Jobs): Custom App
Shopify App Bridge: Die Brücke zwischen beiden Welten
App Bridge erlaubt dir, im Theme JavaScript zu schreiben, das mit einer Custom App kommuniziert, ohne die Seite zu laden.
Beispiel: Theme spricht mit App
// Im Theme-Liquid JavaScript
const client = ShopifyApp.apiClient;
document.querySelector('.add-to-cart').addEventListener('click', async () => {
// Ruft Custom App Backend auf
const response = await fetch('/api/custom-app/add-to-cart-hook', {
method: 'POST',
body: JSON.stringify({ productId: '123', quantity: 1 })
});
const result = await response.json();
if (result.allowed) {
// Weitermachen mit Standard-Checkout
} else {
alert(result.reason);
}
});
Vorteil: Theme-UI mit App-Logic. Beste Welten kombiniert.
Admin API: Wann braucht deine App Schreib-Zugriff?
Die Admin API hat zwei Scopes:
- read_products: Daten lesen
- write_products: Daten schreiben
Schreib-Zugriff ist teuer – kostet mehr API-Calls und braucht mehr Testing.
Brauchst du Schreib-Zugriff für:
- ❌ Daten anzeigen (nur Lese-Zugriff)
- ❌ Filtered List anzeigen (nur Lese-Zugriff)
- ✅ Produkte erstellen/editieren (Schreib-Zugriff!)
- ✅ Inventory-Sync (Schreib-Zugriff!)
- ✅ Automatische Discount-Erstellung (Schreib-Zugriff!)
Fehler, die wir oft sehen
Fehler 1: "Ich baue alles in eine massive Custom App"
Problem: 5000+ Zeilen, schwer zu testen, Deployment-Angst.
Lösung: Aufteilen nach Verantwortlichkeit. Bekateq's 4400 Zeilen waren hochgradig fokussiert.
Fehler 2: "Ich benutze Liquid für sichere Operationen"
Problem: Customer-Daten, Rabatte, Pricing – alle sichtbar im Quelltext.
Lösung: Alles, das sensitiv ist, gehört in eine Custom App.
Fehler 3: "Rate Limits? Kümmern wir uns später drum"
Problem: App läuft langsam, bekommt 429er, Kunden sehen Fehler.
Lösung: Von Tag 1: Rate Limit Handling implementieren.
Unsere Architektur-Prinzipien bei smplx.
1. Liquid für Presentation Theme-Code ist nur für UI. Keine geschäftskritische Logik.
2. Admin API nur wenn nötig Lese-Zugriff wo möglich, Schreib-Zugriff nur für echte Admin-Operationen.
3. Webhooks für Automation Nicht alle 10 Sekunden die API abfragen – Webhooks triggern die Logik.
4. Testing-Kultur Custom Apps müssen automatisiert testbar sein.
5. Minimale Apps Eine Verantwortlichkeit pro App. Ein großer klarer Purpose.
Die Entscheidungs-Checkliste
Frag dich selbst:
- Ist das Daten-sensitiv? → Custom App
- Brauche ich Admin API Zugriff? → Custom App
- Läuft das automatisch (Webhook)? → Custom App
- Muss das super-schnell sein (< 200ms)? → Theme
- Ist das nur UI-Anzeige? → Theme
- Testbar sein? → Custom App
- Braucht es Infrastruktur-Monitoring? → Custom App
Wenn mehr als 2 Punkte auf Custom App hindeuten → Custom App.
Kosten: Theme vs. Custom App
Theme-Development:
- €10-50k Einmalige Kosten
- €100-500/Monat Wartung
- Niedrige Skalierungskosten
Custom App:
- €30-150k Einmalige Entwicklung
- €500-2000/Monat Infrastruktur + Monitoring
- Höhere Skalierungskosten (pro Request)
Aber: Eine Custom App mit klarem Purpose ist günstiger als 3 Theme-Hacks, die du jeden Monat fixen musst.
Long-term Perspective
Nach 2-3 Jahren sieht es so aus:
Theme-Only Approach:
Year 1: Cost: $30k (Dev) + $1.2k (Hosting)
Year 2: Cost: $10k (Bug-Fixes) + $1.2k (Hosting)
Year 3: Cost: $20k (Big Feature) + $1.2k (Hosting)
Total: $64.6k
Problem: Code wurde zu komplex, kann nicht mehr ändern
App-Based Approach (Bekateq-Style):
Year 1: Cost: $80k (Dev) + $6k (Hosting)
Year 2: Cost: $5k (Bug-Fixes) + $6k (Hosting)
Year 3: Cost: $10k (New Feature) + $6k (Hosting)
Total: $113k
Vorteil: Code ist wartbar, neue Features dauern 5 Tage statt 15
Nach Jahr 3 zahlst du weniger neue Features, weil die Entwicklung 3x schneller ist.
Über den Autor
Claudio Gerlich ist Technical Architect bei smplx. und seit 2020 spezialisiert auf Shopify-Technologie. Mit smplx. haben wir über 50+ Shops architektonisch beraten – von simplen Theme-Anpassungen bis zu komplexen Multi-App-Systemen.
Bekateq ist ein gutes Beispiel: Wir halfen ihnen, ihre App-Architektur von chaotisch zu elegant umzustrukturn.
smplx. ist Shopify Technical Partner (seit 2020) mit Sitz in Coesfeld, NRW.