Коротко: Подключите Cloudflare (бесплатный план) - смените NS-серверы домена, включите кэширование статики. Cloudflare имел PoP в Москве и Франкфурте - пользователи из регионов Казахстана получали контент с ~80ms вместо ~200ms. Для видео и больших файлов - Amazon CloudFront с S3.
Без CDN: задержки из регионов КЗ (2016)
Сервер в Алматы → пользователь в разных городах:
Алматы → 15ms (внутри города)
Астана → 45ms
Шымкент → 55ms
Актобе → 120ms
Атырау → 150ms
Усть-Каменогорск → 130ms
С Cloudflare PoP Москва (ближайший к КЗ):
Алматы → 40ms (через Москву - хуже)
Астана → 60ms
Все города → 50-90ms (лучше для регионов)
В 2016 году у Cloudflare не было PoP в Казахстане - ближайший был в Москве и Франкфурте. Для статичного контента это всё равно давало выигрыш за счёт кэширования у пользователя.
Шаг 1: Подключение Cloudflare
1. Зарегистрироваться на cloudflare.com
2. Add a Site → ввести mysite.kz
3. Cloudflare сканирует текущие DNS-записи
4. Изменить NS-серверы у регистратора:
- Было: ns1.hoster.kz, ns2.hoster.kz
- Стало: alice.ns.cloudflare.com, bob.ns.cloudflare.com
5. Дождаться распространения DNS (1-48 часов)
Cloudflare: правила кэширования
Cloudflare Dashboard → Caching → Page Rules:
Правило 1: Кэшировать статику
URL: *.mysite.kz/static/*
Action: Cache Level = Cache Everything
Edge Cache TTL: 1 month
Правило 2: НЕ кэшировать API
URL: *.mysite.kz/api/*
Action: Cache Level = Bypass
Правило 3: НЕ кэшировать корзину и личный кабинет
URL: *.mysite.kz/cart*
Action: Cache Level = Bypass
URL: *.mysite.kz/account*
Action: Cache Level = Bypass
nginx: заголовки для CDN
# Статические файлы - разрешить кэширование в CDN
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
# immutable = браузер не проверяет обновление до истечения TTL
}
# HTML страницы - условное кэширование
location / {
add_header Cache-Control "public, max-age=300, s-maxage=600";
# max-age=300 - браузер кэширует 5 минут
# s-maxage=600 - CDN (Cloudflare) кэширует 10 минут
# s-maxage переопределяет max-age только для CDN
}
# API - запретить кэширование
location /api/ {
add_header Cache-Control "no-store, no-cache, private";
}
# Версионированные ассеты (hash в имени файла)
# app.a1b2c3d4.css - можно кэшировать вечно
location ~* \.[a-f0-9]{8}\.(css|js)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Версионирование ассетов (Cache Busting)
<?php
// PHP: добавлять hash к URL статических файлов
class Assets {
private static array $manifest = [];
public static function url(string $path): string {
// Загрузить манифест версий (генерируется при сборке)
if (empty(self::$manifest)) {
$manifestPath = public_path('build/manifest.json');
if (file_exists($manifestPath)) {
self::$manifest = json_decode(file_get_contents($manifestPath), true);
}
}
if (isset(self::$manifest[$path])) {
return '/build/' . self::$manifest[$path];
}
// Fallback: добавить timestamp
return $path . '?v=' . filemtime(public_path($path));
}
}
// build/manifest.json - генерируется Webpack при сборке
{
"css/app.css": "css/app.a1b2c3d4.css",
"js/app.js": "js/app.e5f6g7h8.js",
"js/vendor.js": "js/vendor.i9j0k1l2.js"
}
<!-- В шаблоне -->
<link rel="stylesheet" href="<?= Assets::url('css/app.css') ?>">
<!-- Вывод: /build/css/app.a1b2c3d4.css - кэшируется вечно -->
Amazon CloudFront для медиафайлов
<?php
// MediaUploader.php - загрузка изображений в S3 + CloudFront
use Aws\S3\S3Client;
class MediaUploader {
private S3Client $s3;
private string $bucket;
private string $cdnUrl; // CloudFront distribution URL
public function upload(string $localPath, string $s3Key): string {
// Загрузить в S3
$this->s3->putObject([
'Bucket' => $this->bucket,
'Key' => $s3Key,
'SourceFile' => $localPath,
'ContentType' => mime_content_type($localPath),
'CacheControl' => 'public, max-age=31536000',
'ACL' => 'public-read',
]);
// Вернуть CloudFront URL (не S3 URL - CDN кэширует)
return $this->cdnUrl . '/' . $s3Key;
// https://d1234abcd.cloudfront.net/products/image.jpg
}
public function getThumbUrl(string $s3Key, int $width, int $height): string {
// Параметры трансформации через CloudFront Lambda@Edge (2017+)
// В 2016: хранить несколько размеров заранее
$thumbKey = str_replace('.jpg', "_{$width}x{$height}.jpg", $s3Key);
return $this->cdnUrl . '/' . $thumbKey;
}
}
Инвалидация CDN кэша при обновлении
<?php
// При обновлении товара - инвалидировать CDN кэш страницы
class CacheInvalidator {
public function invalidateProductPage(int $productId): void {
// Cloudflare API: очистить кэш конкретного URL
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cloudflare.com/client/v4/zones/' . CF_ZONE_ID . '/purge_cache',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Auth-Email: ' . CF_EMAIL,
'X-Auth-Key: ' . CF_API_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'files' => [
'https://mysite.kz/products/' . $productId,
'https://mysite.kz/products/' . $productId . '/',
'https://mysite.kz/catalog/', // Список товаров тоже обновить
]
]),
]);
curl_exec($curl);
curl_close($curl);
}
}
Результаты после CDN (2016, KZ)
Интернет-магазин, сервер в Алматы, Cloudflare + S3/CloudFront:
| Метрика | Без CDN | С CDN |
|---|---|---|
| Загрузка страницы (Алматы) | 1.8с | 1.2с |
| Загрузка страницы (Астана) | 3.1с | 1.6с |
| Загрузка страницы (Актобе) | 4.8с | 2.1с |
| Трафик на origin сервер | 100% | 18% (82% из CDN кэша) |
| Стоимость сервера | базовая | -30% (меньше нагрузки) |
CDN уменьшил трафик на сервер на 82%. Это сказалось на стоимости хостинга - можно было использовать сервер меньшей мощности.