\n// \n```\n\n## 6. Spring Boot application.properties\n\n```properties\n# Время кэширования статических ресурсов (в секундах)\nspring.web.resources.cache.period=31536000\n\n# Или используйте Duration\nspring.web.resources.cache.cachecontrol.max-age=365d\nspring.web.resources.cache.cachecontrol.cache-public=true\n\n# Отключить кэширование в development\nspring.web.resources.cache.cachecontrol.max-age=0\nspring.web.resources.cache.cachecontrol.must-revalidate=true\n```\n\n## 7. JavaScript для работы с кэшом браузера\n\nНе забывайте, что вы контролируете это из бэкенда через заголовки, но вот как браузер это использует:\n\n```javascript\n// Браузер автоматически следит за Cache-Control\n// Вы можете программно очистить кэш\n\nif ('caches' in window) {\n caches.keys().then(cacheNames => {\n cacheNames.forEach(cacheName => {\n caches.delete(cacheName);\n });\n });\n}\n\n// Service Worker для расширенного кэширования\nself.addEventListener('fetch', (event) => {\n event.respondWith(\n caches.match(event.request)\n .then((response) => {\n return response || fetch(event.request);\n })\n );\n});\n```\n\n## 8. Когда кэшировать, а когда нет\n\n| Тип данных | Cache-Control | Причина |\n|-----------|---------------|---------|\n| **JS/CSS/Шрифты** | max-age=365d | Статические, версионируются в URL |\n| **Изображения** | max-age=30d | Редко меняются |\n| **API данные** | max-age=1h | Может быть устаревшей |\n| **Пользовательский профиль** | max-age=1h, private | Персональные данные |\n| **Пароли/Платежи** | no-store | Никогда не кэшировать |\n| **Динамический контент** | no-cache, must-revalidate | Часто обновляется |\n\n## 9. Отладка кэширования\n\n```bash\n# Проверить заголовки ответа\ncurl -i https://api.example.com/api/data\n\n# Увидите:\n# Cache-Control: public, max-age=3600\n# ETag: \"abc123\"\n# Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT\n```\n\n## Итоговая стратегия кэширования\n\n1. **Статические ресурсы** — max-age=365d (долгий период)\n2. **API данные** — max-age=1h + must-revalidate (проверка обновлений)\n3. **Пользовательские данные** — max-age=1h + private (в браузере только)\n4. **Чувствительные данные** — no-store (никогда)\n5. **Часто меняющиеся** — no-cache (всегда проверять)\n\nПравильная стратегия кэширования может снизить нагрузку на сервер на 80% и ускорить приложение в 2-3 раза!","dateCreated":"2026-03-22T15:34:04.581997","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Как управлять кэшом на уровне запросов в браузере

2.0 Middle🔥 131 комментариев
#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление кэшом на уровне запросов в браузере: полный разбор

Большинство современных приложений требуют оптимизации сетевых запросов. Кэширование на уровне браузера — это один из самых эффективных способов улучшить производительность. Давайте разберёмся, как это работает и как это настраивать из бэкенда.

1. HTTP Headers для управления кэшом

Cache-Control Header (Основной инструмент)

@RestController
@RequestMapping("/api/posts")
public class PostController {
    
    @GetMapping("/{id}")
    public ResponseEntity<Post> getPost(@PathVariable String id) {
        Post post = postService.findById(id);
        
        // Кэшируем на 1 час в браузере
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS)
                .cachePublic())
            .body(post);
    }
}

Основные директивы Cache-Control:

Cache-Control: public, max-age=3600
  • public — кэш может быть сохранён браузером и промежуточными прокси
  • private — кэш только для браузера, не для прокси
  • max-age=3600 — кэш действителен 3600 секунд (1 час)
  • no-cache — проверять на сервере перед использованием
  • no-store — не кэшировать совсем
  • must-revalidate — после истечения max-age, обязательно проверить на сервере

Примеры конфигураций

@RestController
@RequestMapping("/api")
public class CacheExampleController {
    
    // 1. Статические данные - кэшируем на длительный период
    @GetMapping("/config/app")
    public ResponseEntity<AppConfig> getAppConfig() {
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)
                .cachePublic())
            .body(appConfigService.getConfig());
    }
    
    // 2. Данные пользователя - приватный кэш
    @GetMapping("/user/profile")
    public ResponseEntity<User> getUserProfile() {
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS)
                .cachePrivate())
            .body(userService.getCurrentUser());
    }
    
    // 3. Чувствительные данные - не кэшируем
    @GetMapping("/payment/card")
    public ResponseEntity<PaymentCard> getPaymentCard() {
        return ResponseEntity.ok()
            .cacheControl(CacheControl.noStore())
            .body(paymentService.getCard());
    }
    
    // 4. Данные, часто обновляемые - проверяем валидность
    @GetMapping("/stock/prices")
    public ResponseEntity<StockPrices> getStockPrices() {
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES)
                .mustRevalidate())
            .body(stockService.getPrices());
    }
}

2. ETag и валидация кэша

ETag позволяет браузеру проверить, изменился ли контент, без загрузки всего ресурса.

@RestController
@RequestMapping("/api/posts")
public class PostController {
    
    @GetMapping("/{id}")
    public ResponseEntity<Post> getPost(
            @PathVariable String id,
            @RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
        
        Post post = postService.findById(id);
        String eTag = generateETag(post);  // Хеш контента
        
        // Если браузер отправил тот же ETag, контент не изменился
        if (eTag.equals(ifNoneMatch)) {
            // Возвращаем 304 Not Modified (без тела)
            return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
                .eTag(eTag)
                .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
                .build();
        }
        
        // Контент изменился, отправляем полный ответ
        return ResponseEntity.ok()
            .eTag(eTag)
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
            .body(post);
    }
    
    private String generateETag(Post post) {
        // Эконом версия
        return "\"" + post.getId().hashCode() + "\"";
        // Или используйте: DigestUtils.md5DigestAsHex(post.toString().getBytes())
    }
}

Как это работает:

  1. Браузер получает ETag: ETag: "abc123"
  2. Браузер кэширует контент
  3. При следующем запросе браузер отправляет: If-None-Match: "abc123"
  4. Если ETag совпадает, сервер возвращает 304 Not Modified
  5. Браузер использует кэшированный контент

3. Last-Modified и If-Modified-Since

@GetMapping("/{id}")
public ResponseEntity<Post> getPost(
        @PathVariable String id,
        @RequestHeader(value = "If-Modified-Since", required = false) 
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime ifModifiedSince) {
    
    Post post = postService.findById(id);
    LocalDateTime lastModified = post.getUpdatedAt();
    
    // Если ресурс не был изменён после указанного времени
    if (ifModifiedSince != null && !lastModified.isAfter(ifModifiedSince)) {
        return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
            .lastModified(lastModified)
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
            .build();
    }
    
    return ResponseEntity.ok()
        .lastModified(lastModified)
        .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
        .body(post);
}

4. Глобальная конфигурация кэширования

@Configuration
public class CacheConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // Статические ресурсы (JS, CSS, шрифты) - кэшируем на долгий период
        registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
                .cachePublic()
                .immutable());  // Контент никогда не изменится
        
        // Изображения - кэшируем, но проверяем
        registry.addResourceHandler("/images/**")
            .addResourceLocations("classpath:/images/")
            .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS)
                .cachePublic()
                .mustRevalidate());
    }
}

5. Более сложный пример с версионированием

@RestController
@RequestMapping("/api/v1")
public class ApiController {
    
    // Версионированный контент можно кэшировать очень долго
    @GetMapping("/data/v{version}")
    public ResponseEntity<Data> getVersionedData(@PathVariable int version) {
        Data data = dataService.findByVersion(version);
        
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
                .cachePublic()
                .immutable())  // URL изменится при обновлении версии
            .body(data);
    }
}

// В HTML файл подставляется правильная версия ресурса
// <script src="/js/app.v1.js"></script>
// <link rel="stylesheet" href="/css/style.v1.css">

6. Spring Boot application.properties

# Время кэширования статических ресурсов (в секундах)
spring.web.resources.cache.period=31536000

# Или используйте Duration
spring.web.resources.cache.cachecontrol.max-age=365d
spring.web.resources.cache.cachecontrol.cache-public=true

# Отключить кэширование в development
spring.web.resources.cache.cachecontrol.max-age=0
spring.web.resources.cache.cachecontrol.must-revalidate=true

7. JavaScript для работы с кэшом браузера

Не забывайте, что вы контролируете это из бэкенда через заголовки, но вот как браузер это использует:

// Браузер автоматически следит за Cache-Control
// Вы можете программно очистить кэш

if ('caches' in window) {
    caches.keys().then(cacheNames => {
        cacheNames.forEach(cacheName => {
            caches.delete(cacheName);
        });
    });
}

// Service Worker для расширенного кэширования
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((response) => {
                return response || fetch(event.request);
            })
    );
});

8. Когда кэшировать, а когда нет

Тип данныхCache-ControlПричина
JS/CSS/Шрифтыmax-age=365dСтатические, версионируются в URL
Изображенияmax-age=30dРедко меняются
API данныеmax-age=1hМожет быть устаревшей
Пользовательский профильmax-age=1h, privateПерсональные данные
Пароли/Платежиno-storeНикогда не кэшировать
Динамический контентno-cache, must-revalidateЧасто обновляется

9. Отладка кэширования

# Проверить заголовки ответа
curl -i https://api.example.com/api/data

# Увидите:
# Cache-Control: public, max-age=3600
# ETag: "abc123"
# Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT

Итоговая стратегия кэширования

  1. Статические ресурсы — max-age=365d (долгий период)
  2. API данные — max-age=1h + must-revalidate (проверка обновлений)
  3. Пользовательские данные — max-age=1h + private (в браузере только)
  4. Чувствительные данные — no-store (никогда)
  5. Часто меняющиеся — no-cache (всегда проверять)

Правильная стратегия кэширования может снизить нагрузку на сервер на 80% и ускорить приложение в 2-3 раза!

Как управлять кэшом на уровне запросов в браузере | PrepBro