← Terug naar kennisbank PERFORMANCE

functions.php hacks die je site slopen — top 5 valkuilen

Iemand kopieert een snippet van een blog of Stack Overflow, plakt 'm in functions.php via Appearance → Theme File Editor, klikt Save, en de site is wit. Ik zie dit elke maand minstens drie keer. Vijf veelvoorkomende fouten — wat ze breken, waarom ze toch overal aanbevolen worden, hoe je uit de mess komt zonder paniek, en wat je had moeten doen.

Eerst: wat te doen als je site nu al wit is

Als je dit artikel leest na een functions.php-bewerking die alles heeft gesloopt: hier is de noodprocedure.

  1. Open FTP/SFTP/cPanel-bestandsbeheer. De Theme Editor in wp-admin is niet meer bereikbaar als je site stuk is.
  2. Navigeer naar /wp-content/themes/[jouw-thema]/functions.php
  3. Open het bestand en verwijder de regels die je net hebt toegevoegd. Of upload een backup-versie als je die hebt.
  4. Sla op. Refresh je site.

Geen FTP-toegang? Vraag het aan je hoster — vrijwel elke NL-hoster geeft je in 1-2 uur SFTP-rechten als je een ticket aanmaakt. Of als je SSH hebt, hernoem het thema-mapje tijdelijk via mv themes/jouw-thema themes/jouw-thema-broken. WordPress valt dan terug op een default-thema en je site werkt weer (lelijk maar bereikbaar).

Met de site weer bereikbaar — laten we kijken naar de vijf hacks die ze het meest sloopten.

Hack 1: session_start() in functions.php

Komt vaak van blogs die uitleggen "hoe je een variabele tussen pageloads onthoudt in WordPress":

// FOUT
add_action('init', 'start_session');
function start_session() {
    if (!session_id()) {
        session_start();
    }
}

Wat het breekt

  • REST API breaks: session_start() stuurt een Set-Cookie-header. WP REST API stuurt JSON, en clients verwachten een Content-Type: application/json zonder cookies. Sommige plugins geven dan onverklaarbare 401's of 500's.
  • Page-caching breekt: WP Rocket, W3 Total Cache, LiteSpeed Cache — allemaal bypassen ze cached pages voor logged-in users en voor sessies. Met session_start() krijgt élke bezoeker een sessie-cookie en wordt cache effectief uitgeschakeld.
  • WooCommerce-conflicten: WC heeft eigen sessie-management. Gebruik PHP-sessions parallel en je krijgt rare cart-bugs (verloren producten, verkeerde gebruiker-data).
  • Cloudflare/Varnish caching breekt: cookies-aanwezig betekent geen edge-cache, dus elk verzoek raakt je origin direct.

Wat je in plaats daarvan moet doen

Voor data tussen pageloads onthouden — gebruik WordPress's eigen mechanismes:

  • User meta: update_user_meta($user_id, 'mijn_variabele', $waarde) voor logged-in users.
  • Transients: set_transient('mijn_key', $waarde, 3600) voor tijdelijke server-side cache.
  • Cookies via WP-API: wp_set_auth_cookie() voor authenticated state.
  • WooCommerce session: WC()->session->set('mijn_key', $waarde) als je in een WC-context werkt.

Hack 2: wp_redirect() zonder voorwaarde of zonder exit()

Komt vaak voor in snippets die "alle bezoekers naar een bepaalde pagina sturen onder voorwaarde X":

// FOUT
add_action('init', 'redirect_alle_bezoekers');
function redirect_alle_bezoekers() {
    wp_redirect('https://nieuwe-pagina.nl');
    // ontbrekende: exit();
}

Wat het breekt

  • Je sluit jezelf buiten admin: zonder voorwaarde (zoals !is_admin() && !is_login_page()) wordt zelfs /wp-admin geredirect. Geen login meer mogelijk.
  • Zonder exit() blijft WordPress code uitvoeren: meestal komt er nog een 2e header() voorbij die conflict geeft, of er wordt body-content gegenereerd waardoor de redirect mislukt.
  • Headers already sent errors als de output al begonnen is.

Hoe je uit de mess komt

Tip: voeg ?nopreview=1 aan een URL waar je redirect naartoe wijst — vaak zit daar geen redirect-actie op. Of, beter, herstel functions.php via FTP.

Als je daadwerkelijk binnen wp-admin zit en je hebt jezelf eruit geredirect: verwijder de cookies van je browser-tab en probeer /wp-login.php?action=logout direct — soms ontwijkt dat de redirect.

Wat je in plaats daarvan moet doen

add_action('template_redirect', 'redirect_alleen_frontend');
function redirect_alleen_frontend() {
    // Alleen op specifieke pagina's, alleen op frontend, niet voor logged-in users
    if (!is_admin() && !is_user_logged_in() && is_page('oude-pagina')) {
        wp_safe_redirect('https://nieuwe-pagina.nl', 301);
        exit;
    }
}

Drie verschillen: hook is template_redirect (loopt alleen op frontend, niet wp-admin), gebruik wp_safe_redirect() (controleert dat je naar je eigen domein redirect, anti-open-redirect), en exit; direct na de redirect.

Hack 3: oneindige loop in save_post

De klassieker — iemand wil bij elke save automatisch een veld bijwerken:

// FOUT
add_action('save_post', 'mijn_save_handler');
function mijn_save_handler($post_id) {
    update_post_meta($post_id, 'last_modified_by', wp_get_current_user()->display_name);
    wp_update_post([
        'ID' => $post_id,
        'post_title' => 'Bijgewerkt: ' . get_the_title($post_id),
    ]);
}

Wat het breekt

wp_update_post() triggert opnieuw save_post → die roept wp_update_post() opnieuw → infinite loop. PHP-script stopt op de max_execution_time (meestal 30-60 sec). Resultaat:

  • Save-knop in admin lijkt eindeloos te laden, dan timeout
  • 500-errors in error_log
  • Soms partial updates die de post-titel honderden keren prefixen ("Bijgewerkt: Bijgewerkt: Bijgewerkt: ...")
  • Database-corruptie als de loop wel afbreekt midden in een transactie

Wat je in plaats daarvan moet doen

Verwijder tijdelijk je eigen action vóór je wp_update_post() aanroept:

add_action('save_post', 'mijn_save_handler');
function mijn_save_handler($post_id) {
    // Voorkom autosave-trigger
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (wp_is_post_revision($post_id)) return;

    update_post_meta($post_id, 'last_modified_by', wp_get_current_user()->display_name);

    // BELANGRIJK: hook tijdelijk losmaken
    remove_action('save_post', 'mijn_save_handler');

    wp_update_post([
        'ID' => $post_id,
        'post_title' => 'Bijgewerkt: ' . get_the_title($post_id),
    ]);

    // Hook weer terugzetten
    add_action('save_post', 'mijn_save_handler');
}

Drie standaard checks: skip autosave, skip revisions, en remove_action + add_action rond je eigen wp_update_post()-aanroep.

Hack 4: zware database-queries in een hook die op elke pageload draait

Subtieler dan de vorige drie. Code die op zich werkt, maar 99% van de pagina-bezoekers vertraagt:

// FOUT
add_action('init', 'check_iets_in_database');
function check_iets_in_database() {
    global $wpdb;
    $resultaten = $wpdb->get_results("
        SELECT * FROM {$wpdb->posts} p
        JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
        WHERE pm.meta_key = 'mijn_field'
    ");
    // doe iets met de resultaten...
}

Wat het breekt

De init-hook draait op elke pageload — frontend, wp-admin, REST API-calls, AJAX, cron. Bovenstaande query joint twee grote tabellen zonder LIMIT en zonder caching. Bij een site met 5.000 posts:

  • Per pageload 200-500ms extra queries-tijd
  • Database-server-load schiet omhoog bij piekmomenten
  • Op shared hosting raak je sneller je max_user_connections aan
  • Plugins en themes die hun eigen queries doen kunnen elkaar niet meer bedienen → "Error establishing a database connection"

Wat je in plaats daarvan moet doen

Drie regels:

  1. Cache het resultaat met transients of object-cache:
function check_iets_in_database() {
    $cached = get_transient('mijn_check_resultaat');
    if ($cached !== false) {
        return $cached;
    }

    global $wpdb;
    $resultaten = $wpdb->get_results("SELECT ... LIMIT 100"); // ALTIJD een LIMIT

    set_transient('mijn_check_resultaat', $resultaten, HOUR_IN_SECONDS);
    return $resultaten;
}
  1. Run alleen waar nodig: niet op init maar op een specifiekere hook zoals template_redirect (alleen frontend), wp_dashboard_setup (alleen dashboard), of een aangepaste shortcode.
  2. Gebruik nooit SELECT * — alleen de kolommen die je echt nodig hebt.

Meer over performance-killers in de database: wp_options autoload — sluipmoordenaar van trage WP-sites.

Hack 5: complete plugin-functionaliteit in functions.php

Niet zozeer een bug, maar een fundamenteel verkeerde keuze. Soms heeft een functions.php 500+ regels code: custom post types, custom fields, login-hardening, e-mail-templates, REST API endpoints, page builders. Allemaal in één bestand.

Wat het breekt

  • Bij thema-wissel verdwijnt alles: nieuwe theme = lege functions.php = al je custom post types weg, custom fields weg, etc. Klanten denken dat hun data weg is. Het staat er nog wel, maar niet meer zichtbaar.
  • Update-conflict met thema-updates: als je een third-party-thema gebruikt, overschrijft elke update jouw functions.php. Aanbevolen workaround: child-theme — maar veel mensen vergeten dat.
  • Geen versiebeheer: één enorm bestand met gemixte functionaliteit is bijna niet meer te onderhouden. Bug-tracking wordt een nachtmerrie.
  • Niet portabel: je kunt niet gemakkelijk dezelfde functionaliteit op een andere site activeren.

Wat je in plaats daarvan moet doen

Hardcoded simpele regel: als de code > 100 regels is of als 'ie níets met thema-presentatie te maken heeft, hoort het in een plugin.

Een "site-specific plugin" aanmaken is 30 seconden werk:

  1. Maak /wp-content/plugins/jouwsite-functionaliteit/jouwsite-functionaliteit.php
  2. Plak deze header bovenin:
<?php
/*
Plugin Name: Jouwsite functionaliteit
Description: Site-specifieke aanpassingen — custom post types, hooks, etc.
Version: 1.0
Author: Daniel Mulder
*/

// Verplicht — voorkomt directe toegang via URL
if (!defined('ABSPATH')) exit;

// Hier al je custom code uit functions.php
  1. Activeer hem in wp-admin → Plugins.
  2. Verplaats al je site-specifieke code uit functions.php hierheen.

Voordelen:

  • Onafhankelijk van thema — wisselen is risicoloos
  • Aan/uit te zetten via WP admin — handig bij debugging
  • Te git-versiebeheren als losse map
  • Te kopiëren naar andere sites

In functions.php blijft alleen wat écht thema-gerelateerd is: custom post-thumbnails, custom after_setup_theme-hooks, theme-supports, layout-aanpassingen.

Eervolle vermeldingen — andere veelvoorkomende fouten

  • error_reporting(E_ALL); ini_set('display_errors', 1); — debugging-instellingen die per ongeluk in productie blijven staan. Lekt PHP-paden, plugin-versies, database-namen. Gebruik in plaats daarvan WP_DEBUG + WP_DEBUG_LOG.
  • Custom autoloader die plugin-functionaliteit overneemt — meestal door een ontwikkelaar die "modern PHP" wilt brengen. Conflict met andere plugins die ook autoloaders hebben.
  • Hardcoded admin-bypass zoals $current_user->add_role('administrator') in init — gebeurt vaker dan je denkt, geeft elke logged-in user admin-rechten.
  • Disabling automatische updates met onbekende constanten: define('WP_AUTO_UPDATE_CORE', false) in functions.php werkt niet (moet in wp-config.php). Resultaat: je site is "uit de updates" maar de updates lopen gewoon door.
  • Filter-callbacks die ALTIJD worden uitgevoerd zonder return-waarde: add_filter('the_content', function($content) { /* doe iets */ }) zonder return $content; → alle content verdwijnt van je site.

Vier vuistregels voor functions.php

  1. Voor elke wijziging: maak een backup van het bestand. Niet via Theme Editor, maar via FTP. Hernoem oude versie naar functions-backup.php.txt of zoiets — niet als .php, anders pakt WordPress hem op als template.
  2. Test eerst op staging, nooit direct op productie. Een staging-site opzetten kost je 30 minuten en voorkomt 30 uur paniek per jaar. Zie WordPress staging-site opzetten.
  3. Gebruik child-theme bij third-party themes. Anders overschrijft de volgende thema-update jouw aanpassingen.
  4. Plak nooit code blind van een blog. Lees minstens de eerste comments — vaak staat daar "deze code werkt niet meer sinds WP 6.1" of "let op: dit zorgt voor een infinite loop in combinatie met X".

Hoe je uit een gesloopte functions.php komt — zonder paniek

Niveau 1 — FTP: download functions.php, edit lokaal, upload terug. Werkt altijd.

Niveau 2 — cPanel/Plesk Bestandsbeheer: navigeer naar /wp-content/themes/[thema]/functions.php, klik bewerken, verwijder de slechte regels. Vergelijkbaar met FTP maar in browser.

Niveau 3 — SSH: cd wp-content/themes/jouw-thema && nano functions.php. Voor de comfortabele.

Niveau 4 — WP-CLI: wp theme activate twentytwentyfour om snel naar een default-thema te switchen. Je site werkt dan weer (lelijker), je kunt rustig het probleem analyseren in het defecte thema.

Niveau 5 — Database via phpMyAdmin: UPDATE wp_options SET option_value = 'twentytwentyfour' WHERE option_name = 'template' OR option_name = 'stylesheet'; Daarna refresh, je site staat op default-thema.

Hulp nodig na een functions.php-incident?

Als je site nu 500-errors of een witte pagina geeft en je hebt geen FTP-toegang, of je weet niet welk bestand je moet aanpassen — dan is het waarschijnlijk sneller om hulp te vragen dan zelf rond te tasten. Een verkeerde "fix" kan extra schade doen, vooral als je in de database aan het rommelen bent.