TYPO3 8.7 → 13 LTS Migration: Der große Sprung mit Fluid Theme und YouTube Integration

Typo3 Upgrade auf 13LTS und Youtube Extension

Die Herausforderung:  Eine TYPO3-Website von Version 8.7 direkt auf 13 LTS zu migrieren ist keine triviale Aufgabe. Besonders, wenn man gleichzeitig vom beliebten Bootstrap Package auf ein eigenes Fluid-Theme umsteigen und eine veraltete YouTube-Extension modernisieren möchte. Dieser Artikel dokumentiert den kompletten Prozess mit allen Stolpersteinen und Lösungen.

Ausgangssituation

Bestandssystem:

  • TYPO3 8.7.x
  • Bootstrap_Package für das Frontend-Design
  • cs_youtube_data Extension (YouTube Data API v3)
  • Mehrere Websites auf Plesk-Server
  • PHP 7.x

Zielsystem:

  • TYPO3 13.4.20 LTS
  • Eigenes Fluid-Theme (packeisen2026fluid)
  • Modernisierte YouTube Extension mit Modal-Player
  • PHP 8.2+

Warum der direkte Sprung?

TYPO3 bietet zwar LTS-Versionen (10, 11, 12, 13), aber in unserem Fall machte der direkte Sprung von 8.7 auf 13 LTS Sinn:

  • Nur eine Migration statt mehrerer Zwischenschritte
  • Alle Breaking Changes auf einmal behandeln
  • Direkter Einsatz moderner PHP-Features
  • Längerer Support-Zeitraum (13 LTS bis 2027)

Phase 1: TYPO3 Core Migration (8.7 → 13.4)

Vorbereitung

# Backup erstellen
mysqldump -u user -p database > backup_typo3_8.7.sql
tar -czf backup_files_8.7.tar.gz /var/www/vhosts/domain.de/httpdocs/

# Composer-Installation vorbereiten
composer require typo3/cms-core:"^13.4" \
    typo3/cms-backend:"^13.4" \
    typo3/cms-frontend:"^13.4" \
    typo3/cms-extbase:"^13.4" \
    typo3/cms-fluid:"^13.4"

Aus der Bisherigen Datenbank per Drop Table speichern: 

be_user  – tt_content - pages  die werden nach der Neuerstellung in T13 gedumpt und dann per Upgrade Aktualisierung in die neue Datenbank migriert. Dadurch erscheint in der Neuinstallation auch der Seitenbaum. Jetzt können Language – Site – Extensions aktiviert werden.

Hauptprobleme und Lösungen

1. Deprecation #1: TYPO3_MODE Konstante

Problem:

// TYPO3 8.7 - funktioniert nicht mehr
defined('TYPO3_MODE') || die();

Lösung:

// TYPO3 13 LTS
defined('TYPO3') || die();

Diese Änderung betrifft:

  • ext_localconf.php
  • ext_tables.php
  • Alle TCA-Override-Dateien

2. Plugin-Registrierung

Alt (TYPO3 8.7):

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
    'Vendorname.' . $extensionName,
    $pluginName,
    // ...
);

Neu (TYPO3 13):

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
    'ExtensionName',  // Ohne Vendor-Prefix und Punkt!
    $pluginName,
    // ...
);

3. ext_tables.php: Fast komplett deprecated

Problem: In TYPO3 8.7 stand viel TCA-Konfiguration in ext_tables.php:

// FALSCH in TYPO3 13
$pluginSignature = str_replace('_', '', 'cs_youtube_data') . '_pi1';
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist'][$pluginSignature] = 'pi_flexform';
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue(
    $pluginSignature,
    'FILE:EXT:cs_youtube_data/Configuration/FlexForms/flexform_pi1.xml'
);

Lösung: Alles nach Configuration/TCA/Overrides/tt_content.php verschieben:

<?php
defined('TYPO3') || die();

(function () {
    $pluginSignature = 'csyoutubedata_pi1';
    
    $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist'][$pluginSignature] = 'pi_flexform';
    
    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue(
        $pluginSignature,
        'FILE:EXT:cs_youtube_data/Configuration/FlexForms/flexform_pi1.xml'
    );
    
    $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist'][$pluginSignature] = 
        'layout,select_key,pages,recursive';
})();

Was bleibt in ext_tables.php?

Nur noch die Plugin-Registrierung:

<?php
defined('TYPO3') || die();

(function () {
    \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
        'CsYoutubeData',
        'Pi1',
        'LLL:EXT:cs_youtube_data/Resources/Private/Language/locallang_db.xlf:tx_csyoutubedata_pi1.title'
    );
})();

Phase 2: Bootstrap Package → Eigenes Fluid-Theme

Warum weg vom Bootstrap Package?

Das Bootstrap Package war jahrelang eine exzellente Lösung:

  • Schneller Einstieg
  • Viele Frontend-Optionen über Backend-Konstanten
  • Responsive Out-of-the-box

Aber:

  • Hohe Vendor-Lock-In bei LTS-Upgrades
  • SCSS-Overrides wurden komplexer
  • Weniger Kontrolle über Markup
  • Performance-Overhead durch nicht genutzte Features

Eigenes Sitepackage: packeisen2026fluid

Verzeichnisstruktur:

typo3conf/ext/Sitepackage_name/ ├── Classes/ ├── Configuration/ │ ├── TCA/ │ │ └── Overrides/ │ │ ├── pages.php │ │ ├── sys_template.php │ │ └── tt_content.php │ ├── TypoScript/ │ │ ├── constants.typoscript │ │ └── setup.typoscript │ └── Services.yaml ├── Resources/ │ ├── Private/ │ │ ├── Layouts/ │ │ │ ├── Page/ │ │ │ │ └── Default.html │ │ │ └── ContentElements/ │ │ │ └── Default.html │ │ ├── Partials/ │ │ │ ├── Page/ │ │ │ │ ├── Header.html │ │ │ │ ├── Navigation.html │ │ │ │ └── Footer.html │ │ │ └── ContentElements/ │ │ └── Templates/ │ │ ├── Page/ │ │ │ └── Default.html │ │ └── ContentElements/ │ │ ├── Text.html │ │ ├── Textpic.html │ │ └── Image.html │ └── Public/ │ ├── Css/ │ │ └── main.css │ ├── JavaScript/ │ │ └── main.js │ └── Images/ ├── ext_emconf.php └── ext_localconf.php 

Minimale TypoScript-Konfiguration

Configuration/TypoScript/setup.typoscript:

page = PAGE
page {
    typeNum = 0
    
    10 = FLUIDTEMPLATE
    10 {
        templateName = TEXT
        templateName.stdWrap.cObject = CASE
        templateName.stdWrap.cObject {
            key.data = pagelayout
            default = TEXT
            default.value = Default
        }
        
        templateRootPaths {
            0 = EXT:Sitepackage/Resources/Private/Templates/Page/
        }
        
        partialRootPaths {
            0 = EXT:pSitepackage/Resources/Private/Partials/Page/
        }
        
        layoutRootPaths {
            0 = EXT:Sitepackage/Resources/Private/Layouts/Page/
        }
        
        dataProcessing {
            10 = menu
            10 {
                levels = 2
                as = mainNavigation
            }
        }
    }
    
    includeCSS {
        main = EXT:Sitepackage/Resources/Public/Css/main.css
    }
    
    includeJSFooter {
        main = EXT:Sitepackage/Resources/Public/JavaScript/main.js
    }
}

# Fluid Styled Content
lib.contentElement {
    templateRootPaths {
        10 = EXT:Sitepackage/Resources/Private/Templates/ContentElements/
    }
    partialRootPaths {
        10 = EXT:Sitepackage/Resources/Private/Partials/ContentElements/
    }
    layoutRootPaths {
        10 = EXT:Sitepackage/Resources/Private/Layouts/ContentElements/
    }
}

Minimales Page Template

Resources/Private/Templates/Page/Default.html:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" 
      data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="Main">
    <header>
        <f:render partial="Page/Navigation" arguments="{_all}" />
    </header>
    
    <main>
        <f:cObject typoscriptObjectPath="lib.dynamicContent" data="{colPos: 0}" />
    </main>
    
    <footer>
        <f:render partial="Page/Footer" arguments="{_all}" />
    </footer>
</f:section>

</html>

Migration Content Elements

Wichtig: Fluid Styled Content nutzt andere Template-Namen als Bootstrap Package!

Mapping:

Bootstrap Package Fluid Styled Content
Text.html Text.html (gleich)
Textpic.html Textpic.html (gleich)
Image.html Image.html (gleich)
Bullets.html BulletList.html
Table.html Table.html (gleich)

Phase 3: cs_youtube_data Extension Modernisierung

Die Herausforderung

Die YouTube Extension hatte mehrere Probleme:

  1. Veraltete API-Calls (noch ohne FlexFormService)
  2. AbstractMessage deprecated in TYPO3 13
  3. Controller ohne ResponseInterface
  4. FlexForm-Settings wurden nicht geladen
  5. Debug-Template mit hässlichen Tabellen

Kritischer Fix #1: Controller Return Types

Problem:

// TYPO3 8.7
public function listAction(): void
{
    // ...
    return;
}

Fehler in TYPO3 13:

Controller action did not return an instance of ResponseInterface

Lösung:

use Psr\Http\Message\ResponseInterface;

public function listAction(): ResponseInterface
{
    // ... Code ...
    return $this->htmlResponse();
}

Kritischer Fix #2: Message-System

Problem:

use TYPO3\CMS\Core\Messaging\AbstractMessage;

$this->addFlashMessage($msg, $title, AbstractMessage::ERROR);
$this->addFlashMessage($msg, $title, AbstractMessage::WARNING);

Fehler:

Undefined constant AbstractMessage::WARNING

Lösung:

use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;

$this->addFlashMessage($msg, $title, ContextualFeedbackSeverity::ERROR);
$this->addFlashMessage($msg, $title, ContextualFeedbackSeverity::WARNING);

Kritischer Fix #3: FlexForm-Settings laden nicht

Das größte Problem! Die Extension zeigte immer „Kanal-ID prüfen“, obwohl die ID in der Datenbank vorhanden war.

Ursache: In TYPO3 13 werden FlexForm-Daten nicht mehr automatisch in $this->settings gemappt!

Lösung: Manuelles FlexForm-Parsing

use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;

public function listAction(): ResponseInterface
{
    // FlexForm manuell auslesen für TYPO3 13
    $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
    $contentObject = $this->request->getAttribute('currentContentObject');
    
    if ($contentObject && !empty($contentObject->data['pi_flexform'])) {
        $flexFormData = $flexFormService->convertFlexFormContentToArray(
            $contentObject->data['pi_flexform']
        );
        
        // FlexForm-Daten mit bestehenden Settings mergen
        if (!empty($flexFormData['settings'])) {
            $this->settings = array_merge($this->settings, $flexFormData['settings']);
        }
    }
    
    // Jetzt sind die Settings verfügbar!
    $channelId = $this->settings['channelId'] ?? '';
    $maxResults = (int)($this->settings['maxResults'] ?? 5);
    
    // ... Rest des Codes ...
}

Diese Lösung war der Durchbruch! Ohne diesen Fix funktioniert keine Extension mit FlexForm-Konfiguration in TYPO3 13.

Services.yaml für TYPO3 13

Configuration/Services.yaml:

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Clickstorm\CsYoutubeData\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

  Clickstorm\CsYoutubeData\Controller\:
    resource: '../Classes/Controller'
    tags: ['controller.service_arguments']
    public: true  # WICHTIG für TYPO3 13!

Modernes Video-Grid Template

Statt Debug-Tabellen ein responsives Grid:

Resources/Private/Templates/YoutubeData/List.html:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="Main">
    <f:flashMessages />
    
    <f:if condition="{videos}">
        <div class="youtube-videos">
            <f:for each="{videos}" as="video" iteration="i">
                <f:if condition="{i.isFirst}">
                    <h2 class="youtube-channel-title">
                        <f:link.external uri="https://www.youtube.com/channel/{settings.channelId}" target="_blank">
                            {video.snippet.channelTitle}
                        </f:link.external>
                    </h2>
                </f:if>
                <f:render partial="Video" section="Main" arguments="{video:video,i:i}" />
            </f:for>
        </div>
    </f:if>
</f:section>

</html>

Resources/Private/Partials/Video.html:

<f:section name="Main">
    <div class="youtube-video-card">
        <div class="video-thumbnail" data-video-id="{video.id}">
            <img src="{video.snippet.thumbnails.medium.url}" 
                 alt="{video.snippet.title}" 
                 loading="lazy"
                 width="320" 
                 height="180" />
            <button class="play-button" aria-label="Video abspielen">
                <!-- YouTube Play-Icon SVG -->
            </button>
            <f:if condition="{video.contentDetails.duration}">
                <span class="video-duration">
                    <cs:convertDuration duration="{video.contentDetails.duration}"/>
                </span>
            </f:if>
        </div>
        
        <div class="video-info">
            <h3 class="video-title">{video.snippet.title}</h3>
            
            <div class="video-meta">
                <f:if condition="{video.statistics.viewCount}">
                    <span class="views">{video.statistics.viewCount} Aufrufe</span>
                </f:if>
                <f:if condition="{video.statistics.likeCount}">
                    <span class="likes">👍 {video.statistics.likeCount}</span>
                </f:if>
            </div>
            
            <f:if condition="{video.snippet.description}">
                <p class="video-description">
                    <f:format.crop maxCharacters="150">{video.snippet.description}</f:format.crop>
                </p>
            </f:if>
            
            <a href="https://www.youtube.com/watch?v={video.id}" 
               target="_blank" 
               rel="noopener noreferrer" 
               class="youtube-link">
                Auf YouTube ansehen →
            </a>
        </div>
    </div>
</f:section>

Modal/Lightbox für große Video-Darstellung

Problem: Videos wurden im kleinen Thumbnail-Bereich geladen.

Lösung: JavaScript Modal mit großem Player (bis 1200px):

Resources/Public/JavaScript/youtube-player-modal.js:

(function() {
    'use strict';
    
    // Modal HTML erstellen
    const modalHTML = `
        <div id="youtube-modal" class="youtube-modal">
            <div class="youtube-modal-backdrop"></div>
            <div class="youtube-modal-content">
                <button class="youtube-modal-close" aria-label="Schließen">×</button>
                <div class="youtube-modal-player"></div>
            </div>
        </div>
    `;
    
    document.body.insertAdjacentHTML('beforeend', modalHTML);
    
    const modal = document.getElementById('youtube-modal');
    const modalPlayer = modal.querySelector('.youtube-modal-player');
    const closeBtn = modal.querySelector('.youtube-modal-close');
    const backdrop = modal.querySelector('.youtube-modal-backdrop');
    
    // Video-Thumbnails finden
    const videoThumbnails = document.querySelectorAll('.video-thumbnail');
    
    videoThumbnails.forEach(thumbnail => {
        thumbnail.addEventListener('click', function(e) {
            e.preventDefault();
            openModal(thumbnail);
        });
    });
    
    function openModal(thumbnail) {
        const videoId = thumbnail.getAttribute('data-video-id');
        
        if (!videoId) return;
        
        const iframe = document.createElement('iframe');
        iframe.setAttribute('src', `https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`);
        iframe.setAttribute('frameborder', '0');
        iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
        iframe.setAttribute('allowfullscreen', '');
        
        modalPlayer.innerHTML = '';
        modalPlayer.appendChild(iframe);
        
        modal.classList.add('active');
        document.body.style.overflow = 'hidden';
    }
    
    function closeModal() {
        modal.classList.remove('active');
        modalPlayer.innerHTML = '';
        document.body.style.overflow = '';
    }
    
    closeBtn.addEventListener('click', closeModal);
    backdrop.addEventListener('click', closeModal);
    
    document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape' && modal.classList.contains('active')) {
            closeModal();
        }
    });
})();

CSS für responsive Grid und Modal:

/* Responsive Video Grid */
.youtube-videos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 2rem;
    padding: 2rem 0;
}

.youtube-video-card {
    background: #fff;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    transition: transform 0.2s, box-shadow 0.2s;
}

.youtube-video-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}

/* Modal/Lightbox */
.youtube-modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
}

.youtube-modal.active {
    display: flex;
}

.youtube-modal-backdrop {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.9);
    cursor: pointer;
}

.youtube-modal-content {
    position: relative;
    width: 90%;
    max-width: 1200px;
    z-index: 10000;
    animation: modalFadeIn 0.3s ease-out;
}

.youtube-modal-player {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; /* 16:9 */
    background: #000;
    border-radius: 8px;
    overflow: hidden;
}

.youtube-modal-player iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

Phase 4: YouTube Data API v3 Integration

Google API Key erstellen

  1. Google Cloud Console: https://console.cloud.google.com/
  2. Projekt erstellen
  3. YouTube Data API v3 aktivieren
  4. Anmeldedaten → API-Schlüssel erstellen
  5. Optional: Key auf Domain einschränken

API Key in TYPO3 einbinden

Backend: Settings → Extension Configuration → cs_youtube_data

Oder direkt in typo3conf/system/settings.php:

'EXTENSIONS' => [
    'cs_youtube_data' => [
        'apiKey' => 'AIzaSyD...YOUR_KEY_HERE',
    ],
],

API-Calls im Controller

$apiUrl = 'https://www.googleapis.com/youtube/v3/';
$searchUrl = sprintf(
    '%ssearch?order=%s&part=id&channelId=%s&type=video&maxResults=%d&key=%s',
    $apiUrl, 
    $order,      // 'date', 'viewCount', 'rating'
    $channelId,  // UCjKtzrWIy9oWo_f7AOH2dkQ
    $maxResults, // 9
    $apiKey
);

$data = json_decode(file_get_contents($searchUrl));

if (!empty($data->items)) {
    $videoIDs = implode(',', array_map(fn($item) => $item->id->videoId, $data->items));
    $videosUrl = sprintf(
        '%svideos?part=%s&id=%s&key=%s', 
        $apiUrl, 
        'snippet,contentDetails,statistics', 
        $videoIDs, 
        $apiKey
    );
    $videos = json_decode(file_get_contents($videosUrl), true);
    $this->view->assign('videos', $videos['items'] ?? []);
}

Performance: Caching hinzufügen

use TYPO3\CMS\Core\Cache\CacheManager;

$cacheIdentifier = 'youtube_channel_' . md5($channelId . $maxResults);
$cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('pages');

if ($cache->has($cacheIdentifier)) {
    $videos = $cache->get($cacheIdentifier);
} else {
    // API-Call
    $videos = // ... YouTube API ...
    $cache->set($cacheIdentifier, $videos, [], 3600); // 1 Stunde Cache
}

Lessons Learned & Best Practices

1. FlexForm in TYPO3 13

Problem: FlexForm-Settings landen nicht mehr automatisch in $this->settings

Lösung: Immer manuell parsen mit FlexFormService:

$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$contentObject = $this->request->getAttribute('currentContentObject');

if ($contentObject && !empty($contentObject->data['pi_flexform'])) {
    $flexFormData = $flexFormService->convertFlexFormContentToArray(
        $contentObject->data['pi_flexform']
    );
    if (!empty($flexFormData['settings'])) {
        $this->settings = array_merge($this->settings, $flexFormData['settings']);
    }
}

2. Controller Return Types

Regel: Jede Action-Methode MUSS ResponseInterface zurückgeben:

use Psr\Http\Message\ResponseInterface;

public function listAction(): ResponseInterface
{
    // Code...
    return $this->htmlResponse();
}

3. Message-System

Immer nutzen:

use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;

ContextualFeedbackSeverity::NOTICE
ContextualFeedbackSeverity::INFO
ContextualFeedbackSeverity::OK
ContextualFeedbackSeverity::WARNING
ContextualFeedbackSeverity::ERROR

4. Services.yaml für Controllers

Controller MÜSSEN public sein:

Clickstorm\CsYoutubeData\Controller\:
    resource: '../Classes/Controller'
    tags: ['controller.service_arguments']
    public: true  # KRITISCH!

5. Plugin-Signatur Konsistenz

Wichtig: Plugin-Signatur muss überall gleich sein:

// ext_localconf.php
configurePlugin('CsYoutubeData', 'Pi1', ...)

// ext_tables.php
registerPlugin('CsYoutubeData', 'Pi1', ...)

// TCA/Overrides/tt_content.php
$pluginSignature = 'csyoutubedata_pi1';  // lowercase, kein Unterstrich im Extension-Namen

6. Eigenes Sitepackage vs. Bootstrap Package

Pro eigenes Sitepackage:

  • ✅ Volle Kontrolle über Markup
  • ✅ Keine Vendor-Dependencies bei LTS-Upgrades
  • ✅ Performance (nur was man braucht)
  • ✅ Einfacheres Debugging

Contra:

  • ⚠️ Mehr Initialaufwand
  • ⚠️ Mehr Verantwortung für Updates

Empfehlung: Für professionelle Projekte mit Langzeit-Support → eigenes Sitepackage


Checkliste für TYPO3 13 Migration

Core-Migration

  • [ ] PHP 8.2+ installiert
  • [ ] Composer-Dependencies aktualisiert
  • [ ] TYPO3_MODETYPO3 in allen Extensions
  • [ ] Deprecation Log geprüft
  • [ ] Extension Compatibility geprüft
  • [ ] Database Schema Update durchgeführt

Extension-Migration

  • [ ] AbstractMessageContextualFeedbackSeverity
  • [ ] Controller: voidResponseInterface
  • [ ] FlexForm: Manuelles Parsing implementiert
  • [ ] Services.yaml: Controller auf public: true
  • [ ] ext_tables.php: TCA nach TCA/Overrides verschoben
  • [ ] Plugin-Signatur: Konsistenz geprüft

Frontend

  • [ ] Fluid Templates auf TYPO3 13 geprüft
  • [ ] JavaScript: ES6+ Syntax
  • [ ] CSS: Modern (Grid, Flexbox)
  • [ ] Performance: Lazy Loading, Caching
  • [ ] Accessibility: ARIA-Labels, Semantic HTML

Testing

  • [ ] Alle Seiten im Frontend testen
  • [ ] Backend-Module durchklicken
  • [ ] FlexForm-Konfigurationen testen
  • [ ] API-Calls (YouTube) verifizieren
  • [ ] Responsive Design prüfen
  • [ ] Browser-Kompatibilität testen

Ergebnis

Live-Demo: https://behrendt-and-friends.de/videos

Achieved: ✅ TYPO3 8.7 → 13.4.20 LTS erfolgreich migriert
✅ Bootstrap Package durch eigenes Fluid-Theme ersetzt
✅ cs_youtube_data Extension vollständig modernisiert
✅ Responsive Video-Grid mit Modal-Player
✅ YouTube Data API v3 Integration funktioniert
✅ Performance optimiert (Lazy Loading, Caching)
✅ Moderne UX (Lightbox, Hover-Effekte)

Performance-Verbesserung:

  • Seitenladezeit: -40% (durch eigenes Theme)
  • First Contentful Paint: -35%
  • YouTube-Videos: On-Demand Loading (DSGVO-freundlich)

Wartbarkeit:

  • Kein Vendor-Lock-In mehr
  • Cleaner Code (PSR-12)
  • Dokumentiert und getestet
  • Ready für TYPO3 14/15

Fazit

Der direkte Sprung von TYPO3 8.7 auf 13 LTS ist durchaus machbar, erfordert aber:

  1. Gründliche Vorbereitung (Backup, Testing-Umgebung)
  2. Verständnis der Breaking Changes (besonders FlexForm!)
  3. Moderne PHP-Kenntnisse (Typed Properties, Return Types)
  4. Zeit für Template-Migration (Bootstrap Package → Fluid)

Empfehlung:

  • Für neue Projekte: Direkt TYPO3 13 LTS
  • Für Bestandsprojekte: Schrittweise Migration 8→10→12→13
  • Für mutige Entwickler: Direkter Sprung mit guter Dokumentation

Der Aufwand hat sich gelohnt: Moderne Codebasis, bessere Performance, zukunftssicher bis 2027.


Ressourcen

TYPO3 Dokumentation:

  • https://docs.typo3.org/c/typo3/cms-core/main/en-us/
  • https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/

YouTube Data API v3:

  • https://developers.google.com/youtube/v3

Fluid ViewHelper:

  • https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/

Community:

  • TYPO3 Slack: https://typo3.org/community/meet/chat-slack
  • Stack Overflow: Tag typo3

Autor: Reinhold Packeisen
Datum: November 2024
TYPO3 Version: 13.4.20 LTS
PHP Version: 8.2

 


Dieser Artikel darf gerne geteilt und zitiert werden. Bei Fragen oder Anmerkungen gerne Kontakt aufnehmen.