Когато започнах 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' ]
);
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 );
}
Резултат: от ~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 '';
}
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;
}
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' );
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 });
Тънка червена линия в горната част на 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 ) );
}
Native share buttons — нулев external JS
async function sharePost() {
if ( navigator.share ) {
await navigator.share({ title, url });
} else {
await navigator.clipboard.writeText( url );
btn.textContent = 'Копирано ✓';
}
}
Web Share API на мобилни, clipboard fallback на desktop. LinkedIn - обикновен <a href>. Никакъв SDK.
Останалите три без ред JS
-
Related posts —
WP_Queryпо същата категория сorderby=rand, без плъгин -
Sticky sidebar —
position: sticky; top: 2rem— буквално един ред CSS -
Card hover animations —
translateY(-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;
}
Данните идват от 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.' );
}
Резултатът
| Метрика | Стойност |
|---|---|
| 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)