- 6. August 2025
#lessonlearned – Video Untertitel mit VTT-Dateien in Storyblok: So geht's
In unserem Blogbeitrag "Anleitung zum Einbinden von Videos in Storyblok" haben wir gezeigt, wie Sie Videos per Storyblok CMS und Nuxt.js auf Ihrer Website einbinden.
Im nächsten Schritt geht es darum, Ihre Videos um barrierefreie Untertitel zu ergänzen – ein echter Mehrwert und in vielen Fällen gesetzlich vorgeschrieben. Wir zeigen Ihnen, wie Sie Ihrem Video-Block ein Feld für VTT-Untertitel hinzufügen und worauf Sie bei der Integration achten müssen. Außerdem erfahren Sie, wie Sie typische Fehler beim Hinterlegen von Untertitel-Dateien vermeiden und mit einer automatisierten Validierung nur funktionierende Untertitel im Frontend ausgeben.
1. Warum Untertitel? – Barrierefreiheit, rechtliche Anforderungen und bessere User Experience
Video-Untertitel sind mittlerweile ein essenzieller Bestandteil moderner Webseiten. Sie verbessern nicht nur die Nutzererfahrung für Menschen mit Hörbeeinträchtigungen, sondern sind auch hilfreich beim Betrachten von Videos in lauten Umgebungen oder unterwegs – und unter gewissen Voraussetzungen sogar rechtlich verpflichtend. Untertitel sorgen dafür, dass Ihre Inhalte allen Menschen zugänglich gemacht werden können.
2. Erweiterung des Video-Blocks um VTT-Untertitel
Voraussetzung: Sie haben bereits einen Video-Block, wie in unserem Beitrag "#lessonlearned – Anleitung zum Einbinden von Videos in Storyblok" beschrieben.
So erweitern Sie Ihre Videos um Untertitel (Step-by-Step):
Neuen Block für Untertitel anlegen:
Navigieren Sie im Storyblok-Backend ins Block Library Modul und legen Sie einen neuen Nestable Block mit dem NamenVideoSubtitle
an.Felder im Untertitel-Block anlegen:
file
(Asset): Hier laden Redakteure die VTT-Datei hochlabel
(Text): Name des Untertitels, wie im Frontend sichtbar (z.B. „Deutsch“)language_code
(Single Option): Setzen Sie als „required“ (z.B. Option „de“, default „de“)is_default
(Boolean): Gibt an, ob die Untertitel standardmäßig aktiv sind (default: aktiv)
Video-Block erweitern:
Fügen Sie im Video-Block ein Feld
subtitles
vom Typ „Blocks“ hinzu.Erlauben Sie nur
VideoSubtitle
-Blöcke als Sub-Komponenten.
Tipp: Alternativ können Sie das Block-Schema auch direkt als JSON/YAML-Schema in Ihrem Projektverzeichnis anlegen oder erweitern.
3. Der typische Stolperstein: Fehlerhafte Untertitel-Dateien
Bei der Bereitstellung von Untertitel-Dateien passieren häufig Fehler. Beispielsweise können Redakteure versehentlich Dateien im RTF-Format statt im erforderlichen Plain Text-Format .vtt hochladen – häufig, weil viele Texteditoren standardmäßig RTF speichern, wenn nicht explizit „nur Text“ gewählt wird.
Ein Beispiel für eine fehlerhafte VTT-Datei:
{\rtf1\ansi\ansicpg1252\cocoartf2821
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fmodern\fcharset0 Courier;}
WEBVTT
Richtiges Beispiel:
WEBVTT
00:00:00.000 --> 00:00:03.000
Dies ist ein gültiger Untertitel
Das Ergebnis: Fehlerhafte oder korrupt hochgeladene Dateien werden im Frontend nicht (korrekt) angezeigt – die Barrierefreiheit leidet und Nutzer sehen keine oder kaputte Untertitel.
4. Die Lösung: Automatische VTT-Validierung via Proxy
Um sicherzustellen, dass ausschließlich funktionierende VTT-Dateien im Frontend ausgespielt werden, empfiehlt sich eine automatische Prüfung („Validierung“) beim Laden der Datei. Außerdem löst diese Lösung direkt das CORS-Problem (Cross-Origin Resource Sharing), weil Storyblok-Assets häufig von einer externen Domain ausgeliefert werden.
Proxy-Endpoint mit Validierung einrichten
Legen Sie im Ordner server/middleware/
eine Datei vtt-proxy.js
an, die wie folgt arbeitet:
export default defineEventHandler(async (event) => {
const url = event.node.req.url
if (url.includes('/vtt-proxy/')) {
try {
// Storyblok-url aus Pfad extrahieren
const storyblokUrlEncoded = url.split('/vtt-proxy/')[1]
const storyblokUrl = decodeURIComponent(storyblokUrlEncoded)
const response = await fetch(storyblokUrl)
const vttContent = await response.text()
// VTT-Validierung
if (!validateVTTContent(vttContent)) {
return ''
}
// Response-Header setzen
event.node.res.setHeader('Content-Type', 'text/vtt')
event.node.res.setHeader('Access-Control-Allow-Origin', '*')
return vttContent
} catch (error) {
console.error('VTT proxy error:', error)
return ''
}
}
})
function validateVTTContent(content) {
// Prüfe auf WEBVTT-Header
if (!content.startsWith('WEBVTT')) {
return false
}
// Prüfe auf RTF-Artefakte
if (
content.includes('\\rtf1') || content.includes('\\ansi') || content.includes('\\fonttbl') || content.includes('\\colortbl')
) {
return false
}
// Prüfe auf grundlegendes WebVTT Format
const lines = content.split('\n')
// Prüfe auf Zeitstempel-Format
const hasTimestamp = lines.some(line =>
/\d{2}:\d{2}:\d{2}\.\d{3}\s+-->\s+\d{2}:\d{2}:\d{2}\.\d{3}/.test(line)
)
return hasTimestamp
}
5. Integration in die Vue-Komponente
Die Kernfunktion liegt in der Vue-Komponente, die jeden Untertitel vor dem Rendern validiert:
<script setup>
import { computed, ref, onMounted } from 'vue'
const props = defineProps({
blok: { type: Object, required: true, default: () => ({}) }
})
const validatedSubtitles = ref([])
const subtitles = computed(() => props.blok.subtitles || [])
const getProxiedSubtitleUrl = (url) => {
if (!url) return ''
// URL-encode the Storyblok-url and route it via our proxy
return `/vtt-proxy/${encodeURIComponent(url)}`
}
// Kernfunktion: VTT-Validierung
const isVttValid = async (url) => {
if (!url) return false
try {
const proxyUrl = getProxiedSubtitleUrl(url)
const response = await fetch(proxyUrl)
if (!response.ok) return false
const content = await response.text()
// Inhalts-Check
// Proxy gibt leeren String für ungültige VTT-Dateien zurück
if (!content.trim()) {
console.warn(`Empty content returned for ${url}, considering invalid`)
return false
}
return true
} catch (error) {
console.error(`Error validating VTT: ${url}`, error)
return false
}
}
</script>
6. Automatische Filterung beim Komponenten-Mount
Nachdem wir die Validierungsfunktion definiert haben, müssen wir sie beim Laden der Komponente ausführen. So sind am Ende nur gültige Untertitel im Template verfügbar:
<script setup>
// ... vorheriger Code
const validateAllSubtitles = async () => {
const validated = []
for (const subtitle of subtitles.value) {
if (!subtitle?.file?.filename) continue
const filename = subtitle.file.filename
const isValid = await isVttValid(filename)
if (isValid) {
validated.push({
...subtitle,
proxyUrl: getProxiedSubtitleUrl(filename)
})
} else {
console.warn(`Skipping invalid subtitle: ${filename}`)
}
}
validatedSubtitles.value = validated
}
onMounted(() => {
validateAllSubtitles()
})
</script>
<template>
<video
v-editable="props.blok"
:controls="true"
>
<source
v-if="blok.video?.filename"
:src="blok.video.filename"
type="video/mp4"
>
<!-- Nur validierte Untertitel werden gerendert -->
<track
v-for="(subtitle, index) in validatedSubtitles"
:key="index"
kind="subtitles"
:src="subtitle.proxyUrl"
:srclang="subtitle.language_code"
:label="subtitle.label"
:default="subtitle.is_default"
/>
<!-- Fallback für Browser ohne Video-Unterstützung -->
Ihr Browser unterstützt keine HTML5-Videos.
</video>
</template>
Fazit: Robuste VTT-Lösung die barrierefreie Videoausgabe sicherstellt
Mit dieser Lösung wird Ihre Videokomponente um ein nachhaltiges, barrierefreies Feature erweitert: Nicht nur können nun Untertitel hinterlegt werden, es wird auch sichergestellt, dass ausschließlich technisch korrekte VTT-Dateien im Frontend ausgegeben werden.
Dadurch erfüllen Sie gesetzliche Vorgaben zur Barrierefreiheit und bieten Ihren Nutzern die bestmögliche User Experience. Die Validierung erfolgt asynchron und hat keinen negativen Einfluss auf die Performance oder das Ladeverhalten Ihrer Seite.
Tipp: Die allgemeine Einrichtung der Video-Komponente finden Sie in unserem Artikel "#lessonlearned – Anleitung zum Einbinden von Videos in Storyblok".
Wie die Video-Untertitel jetzt aussehen können, sehen Sie auf unserer Startseite. Für Fragen und Anregungen stehen wir Ihnen jederzeit gerne zur Verfügung.