DEV Community

Stanchev
Stanchev

Posted on

Как изградих WordPress тема с Lighthouse 95+ от нулата

Когато започнах PressGrid, целта беше проста: WordPress тема за новинарски сайтове, която не зарежда jQuery, не се нуждае от плъгини за перформанс и минава Lighthouse с 95+.

Повечето теми идват с Bootstrap, jQuery, слайдери и десетки HTTP заявки. За новинарски сайт с висок трафик това е смърт - render-blocking ресурси убиват LCP, всяка допълнителна заявка вдига TTFB.

Решението: нулеви JS зависимости извън WordPress core, ~22KB CSS без framework overhead, и всичко написано ръчно.


Архитектура: какво направи разликата

1. Deferred JS навсякъде

Всички скриптове се зареждат с defer - навигация, progress bar, share бутони, back-to-top. Браузърът парсва HTML без прекъсване. Нито един скрипт не блокира рендиране.

wp_enqueue_script(
    'pressgrid-main',
    get_template_directory_uri() . '/assets/js/main.js',
    [],
    PRESSGRID_VERSION,
    [ 'strategy' => 'defer' ]
);
Enter fullscreen mode Exit fullscreen mode

2. Transient caching + no_found_rows

Hero, trending, category grid - всичко минава през set_transient(). Само post ID-та се кешират, заявката се реконструира от кеша.

$cached = get_transient( 'pressgrid_hero_posts' );

if ( ! $cached ) {
    $query = new WP_Query([
        'posts_per_page' => 4,
        'no_found_rows'  => true, // пропуска SQL_CALC_FOUND_ROWS
        'fields'         => 'ids',
    ]);
    set_transient( 'pressgrid_hero_posts', $query->posts, 5 * MINUTE_IN_SECONDS );
}
Enter fullscreen mode Exit fullscreen mode

Резултат: от ~8 DB заявки при зареждане до 1-2 при cache hit.

3. LCP оптимизация

Hero изображението получава fetchpriority="high" + loading="eager". Всички останали - loading="lazy" + decoding="async". Preconnect hints се добавят само когато съответните функции са активни.

// Preconnect само ако weather widget-ът е включен
if ( get_theme_mod( 'pressgrid_weather_api_key' ) ) {
    echo '';
}
Enter fullscreen mode Exit fullscreen mode

4. CSS без framework - ~22KB

Целият стилинг е нативен CSS Grid + Flexbox. Никакъв Bootstrap. CSS custom properties за theming с live Customizer preview чрез postMessage транспорт - без page reload.

:root {
    --pg-primary:    #cc0000;
    --pg-secondary:  #990000;
    --pg-text:       #1a1a1a;
    --pg-bg:         #ffffff;
    --font-display:  'Playfair Display', Georgia, serif;
    --font-ui:       'Barlow Condensed', Arial, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

SEO от ден едно

Lighthouse Performance е само половината. За новинарски сайт SEO е също толкова важен.

NewsArticle JSON-LD (автоматично)

function pressgrid_newsarticle_schema() {
    if ( ! is_single() ) return;

    $schema = [
        '@context'      => 'https://schema.org',
        '@type'         => 'NewsArticle',
        'headline'      => get_the_title(),
        'datePublished' => get_the_date( 'c' ),
        'dateModified'  => get_the_modified_date( 'c' ),
        'author'        => [ '@type' => 'Person', 'name' => get_the_author() ],
        'publisher'     => [
            '@type' => 'Organization',
            'name'  => get_bloginfo( 'name' ),
        ],
    ];

    echo ''
        . wp_json_encode( $schema )
        . '';
}
add_action( 'wp_head', 'pressgrid_newsarticle_schema' );
Enter fullscreen mode Exit fullscreen mode

Google News изисква NewsArticle schema за Discover. Излиза автоматично на всеки single post - нулева конфигурация.

Open Graph + Twitter Cards на всяка страница

Не само на постове - на архиви, категории, статични страници. Featured image-ът се дърпа автоматично.


Reading UX: 6 неща без нито един плъгин

Reading progress bar

const bar = document.querySelector('.pg-progress');
window.addEventListener('scroll', () => {
    const h = document.documentElement;
    const pct = ( h.scrollTop / ( h.scrollHeight - h.clientHeight ) ) * 100;
    bar.style.width = pct + '%';
}, { passive: true });
Enter fullscreen mode Exit fullscreen mode

Тънка червена линия в горната част на viewport-а. Само на single posts. passive: true — не блокира scroll thread-а.

Estimated read time (server-side)

function pressgrid_reading_time( $post_id ) {
    $content = get_post_field( 'post_content', $post_id );
    $words   = str_word_count( wp_strip_all_tags( $content ) );
    return max( 1, (int) ceil( $words / 200 ) );
}
Enter fullscreen mode Exit fullscreen mode

Native share buttons — нулев external JS

async function sharePost() {
    if ( navigator.share ) {
        await navigator.share({ title, url });
    } else {
        await navigator.clipboard.writeText( url );
        btn.textContent = 'Копирано ✓';
    }
}
Enter fullscreen mode Exit fullscreen mode

Web Share API на мобилни, clipboard fallback на desktop. LinkedIn - обикновен <a href>. Никакъв SDK.

Останалите три без ред JS

  • Related postsWP_Query по същата категория с orderby=rand, без плъгин
  • Sticky sidebarposition: sticky; top: 2rem — буквално един ред CSS
  • Card hover animationstranslateY(-3px) + box-shadow, чист CSS

Forex ticker, който се самоактивира

Едно от любимите ми неща в PressGrid: форекс тикерът се активира автоматично когато Layout Builder има секция с Business/Finance категория. Нулева конфигурация.

function pressgrid_has_business_section() {
    $cached = get_transient( 'pressgrid_has_biz_section' );
    if ( false !== $cached ) return (bool) $cached;

    $slugs    = [ 'business', 'finance', 'бизнес', 'финанси', 'economy', 'пазари' ];
    $sections = get_option( 'pressgrid_layout_sections', [] );
    $result   = false;

    foreach ( $sections as $section ) {
        if ( empty( $section['enabled'] ) || empty( $section['category'] ) ) continue;
        $cat = get_category( $section['category'] );
        if ( $cat && in_array( $cat->slug, $slugs, true ) ) {
            $result = true;
            break;
        }
    }

    set_transient( 'pressgrid_has_biz_section', $result, 5 * MINUTE_IN_SECONDS );
    return $result;
}
Enter fullscreen mode Exit fullscreen mode

Данните идват от Frankfurter (ECB) - безплатно, без API ключ, кешира се 6 часа.


Security hardening

Всеки $_POST минава през wp_verify_nonce() + current_user_can(). Всеки output — esc_html(), esc_url(), esc_attr(). Ad HTML минава през wp_kses() со strict whitelist.

Нещото, което ме радва най-много: font upload-ът валидира WOFF2 magic bytes (0x774F4632) преди да запише файла. Не разчитам само на extension-а.

$handle  = fopen( $tmp_path, 'rb' );
$magic   = fread( $handle, 4 );
fclose( $handle );

if ( $magic !== "\x77\x4F\x46\x32" ) {
    wp_die( 'Invalid WOFF2 file.' );
}
Enter fullscreen mode Exit fullscreen mode

Резултатът

Метрика Стойност
Lighthouse Performance 95+
Render-blocking JS 0
External JS зависимости 0
CSS размер ~22KB
DB заявки при cache hit 1-2
PHP файлове 25
Преводими стрингове 182

Какво следва

В момента работя по Sofia Vital - Next.js 18 проект, където прилагам същите принципи: Core Web Vitals first, structured data, нулев layout shift.

Ако искаш да разгледаш кода на PressGrid:

Въпроси? Drop ги в коментарите — отговарям на всичко.

Top comments (0)