-
-
Notifications
You must be signed in to change notification settings - Fork 1
Performance Optimization
Learn how to optimize your SEO analysis performance with caching, lazy loading, and efficient content processing strategies.
The PHP-SEO package is designed for performance, but when dealing with large amounts of content or high-traffic applications, optimization becomes crucial. This guide covers various strategies to maximize performance while maintaining SEO effectiveness.
- Large HTML Content: Analyzing large HTML documents
- Repeated Analysis: Analyzing the same content multiple times
- External Requests: Fetching remote resources for validation
- Memory Usage: Processing content-heavy pages
- Regex Operations: Complex pattern matching in content analysis
Cache analysis results to avoid reprocessing:
use Rumenx\PhpSeo\SeoManager;
use Psr\SimpleCache\CacheInterface;
class CachedSeoManager
{
private SeoManager $seoManager;
private CacheInterface $cache;
private int $ttl = 3600; // 1 hour
public function __construct(SeoManager $seoManager, CacheInterface $cache)
{
$this->seoManager = $seoManager;
$this->cache = $cache;
}
public function analyze(string $content, array $options = []): SeoAnalysis
{
$cacheKey = $this->generateCacheKey($content, $options);
$result = $this->cache->get($cacheKey);
if ($result !== null) {
return unserialize($result);
}
$analysis = $this->seoManager->analyze($content, $options);
$this->cache->set($cacheKey, serialize($analysis), $this->ttl);
return $analysis;
}
private function generateCacheKey(string $content, array $options): string
{
return 'seo_analysis_' . md5($content . serialize($options));
}
}// In your Laravel service provider
use Illuminate\Support\Facades\Cache;
public function analyzePage(Request $request): array
{
$content = $request->input('content');
$cacheKey = 'seo_analysis_' . md5($content);
return Cache::remember($cacheKey, 3600, function() use ($content) {
return app(SeoManager::class)->analyze($content)->toArray();
});
}// Using Symfony Cache component
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$cache = new FilesystemAdapter('seo_analysis', 3600);
$cacheItem = $cache->getItem($cacheKey);
if (!$cacheItem->isHit()) {
$analysis = $seoManager->analyze($content);
$cacheItem->set($analysis);
$cache->save($cacheItem);
}
$analysis = $cacheItem->get();Only analyze components when needed:
use Rumenx\PhpSeo\Config\SeoConfig;
$config = new SeoConfig([
'performance' => [
'lazy_loading' => true,
'deferred_components' => [
'structured_data',
'social_media_tags',
'content_analysis'
]
]
]);
$seo = new SeoManager($config);
// Analysis components are loaded only when accessed
$analysis = $seo->analyze($content);
$metaTags = $analysis->getMetaTags(); // Lightweight operation
$structuredData = $analysis->getStructuredData(); // Loaded on demandSkip unnecessary analysis based on content type:
class SmartSeoManager extends SeoManager
{
public function analyze(string $content, array $options = []): SeoAnalysis
{
$contentType = $options['content_type'] ?? 'webpage';
$config = $this->getOptimizedConfig($contentType);
return parent::analyze($content, array_merge($options, [
'config' => $config
]));
}
private function getOptimizedConfig(string $contentType): array
{
switch ($contentType) {
case 'api_response':
return [
'analyze_content' => false,
'generate_structured_data' => false,
'check_images' => false
];
case 'product_page':
return [
'analyze_content' => true,
'generate_structured_data' => true,
'schema_types' => ['Product']
];
case 'blog_post':
return [
'analyze_content' => true,
'generate_structured_data' => true,
'schema_types' => ['Article']
];
default:
return [];
}
}
}use Rumenx\PhpSeo\Config\SeoConfig;
$config = new SeoConfig([
'html_parser' => [
'method' => 'dom', // Use DOM instead of regex for complex HTML
'encoding' => 'UTF-8',
'preserve_whitespace' => false,
'load_external_entities' => false
],
'content_analysis' => [
'max_content_length' => 50000, // Limit content analysis length
'skip_large_text_blocks' => true,
'optimize_text_extraction' => true
]
]);class OptimizedContentAnalyzer
{
private array $stopWords;
private array $commonWords;
public function analyzeContent(string $content): array
{
// Limit content length for analysis
if (strlen($content) > 100000) {
$content = $this->extractRelevantContent($content);
}
// Use pre-compiled regex patterns
static $patterns = [
'headings' => '/<h[1-6][^>]*>(.*?)<\/h[1-6]>/is',
'paragraphs' => '/<p[^>]*>(.*?)<\/p>/is',
'links' => '/<a[^>]*href=["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is'
];
$analysis = [];
foreach ($patterns as $type => $pattern) {
preg_match_all($pattern, $content, $matches);
$analysis[$type] = $matches[1] ?? [];
}
return $analysis;
}
private function extractRelevantContent(string $content): string
{
// Extract main content area, skip navigation, footer, etc.
$dom = new DOMDocument();
@$dom->loadHTML($content);
$xpath = new DOMXPath($dom);
// Try to find main content
$mainSelectors = [
'//main',
'//article',
'//*[@id="content"]',
'//*[@class="content"]',
'//body'
];
foreach ($mainSelectors as $selector) {
$elements = $xpath->query($selector);
if ($elements->length > 0) {
return $dom->saveHTML($elements->item(0));
}
}
return $content;
}
}class MemoryEfficientSeoManager
{
private int $memoryLimit;
public function __construct(int $memoryLimit = 128 * 1024 * 1024) // 128MB
{
$this->memoryLimit = $memoryLimit;
}
public function analyzeContent(string $content): array
{
$initialMemory = memory_get_usage();
try {
// Process content in chunks if it's large
if (strlen($content) > 1000000) { // 1MB
return $this->analyzeInChunks($content);
}
$result = $this->performAnalysis($content);
// Clean up if memory usage is high
if (memory_get_usage() - $initialMemory > $this->memoryLimit / 2) {
$this->cleanup();
}
return $result;
} catch (Exception $e) {
$this->cleanup();
throw $e;
}
}
private function analyzeInChunks(string $content): array
{
$chunkSize = 100000; // 100KB chunks
$chunks = str_split($content, $chunkSize);
$results = [];
foreach ($chunks as $chunk) {
$chunkResult = $this->performAnalysis($chunk);
$results[] = $chunkResult;
// Clean up after each chunk
unset($chunkResult);
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles();
}
}
return $this->mergeResults($results);
}
private function cleanup(): void
{
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles();
}
}
}// Laravel Migration Example
Schema::create('seo_cache', function (Blueprint $table) {
$table->id();
$table->string('content_hash', 64)->unique();
$table->json('analysis_result');
$table->timestamp('expires_at');
$table->timestamps();
$table->index(['content_hash', 'expires_at']);
});class BatchSeoProcessor
{
private SeoManager $seoManager;
private int $batchSize = 50;
public function processBatch(array $pages): array
{
$results = [];
$batches = array_chunk($pages, $this->batchSize);
foreach ($batches as $batch) {
$batchResults = $this->processBatchItems($batch);
$results = array_merge($results, $batchResults);
// Small delay to prevent overwhelming the system
usleep(100000); // 0.1 seconds
}
return $results;
}
private function processBatchItems(array $items): array
{
$results = [];
foreach ($items as $item) {
try {
$analysis = $this->seoManager->analyze($item['content']);
$results[$item['id']] = $analysis->toArray();
} catch (Exception $e) {
$results[$item['id']] = ['error' => $e->getMessage()];
}
}
return $results;
}
}use Rumenx\PhpSeo\Config\SeoConfig;
// Optimized configuration for production
$config = new SeoConfig([
'performance' => [
'cache_enabled' => true,
'cache_ttl' => 3600,
'lazy_loading' => true,
'memory_limit' => '256M',
'max_execution_time' => 30
],
'analysis' => [
'deep_content_analysis' => false, // Disable for faster processing
'check_external_links' => false, // Skip external link validation
'generate_suggestions' => false, // Skip suggestion generation
'detailed_metrics' => false // Only basic metrics
],
'html_parser' => [
'method' => 'dom',
'encoding' => 'UTF-8',
'preserve_whitespace' => false
]
]);// Detailed configuration for development/debugging
$config = new SeoConfig([
'performance' => [
'cache_enabled' => false, // Disable cache for testing
'debug_mode' => true, // Enable debugging
'profile_performance' => true // Track performance metrics
],
'analysis' => [
'deep_content_analysis' => true,
'check_external_links' => true,
'generate_suggestions' => true,
'detailed_metrics' => true,
'validate_html' => true
]
]);class PerformanceMonitor
{
private array $metrics = [];
public function startTimer(string $operation): void
{
$this->metrics[$operation] = [
'start_time' => microtime(true),
'start_memory' => memory_get_usage()
];
}
public function endTimer(string $operation): array
{
if (!isset($this->metrics[$operation])) {
return [];
}
$metric = $this->metrics[$operation];
$endTime = microtime(true);
$endMemory = memory_get_usage();
return [
'operation' => $operation,
'execution_time' => $endTime - $metric['start_time'],
'memory_usage' => $endMemory - $metric['start_memory'],
'peak_memory' => memory_get_peak_usage()
];
}
public function getMetrics(): array
{
return $this->metrics;
}
}
// Usage
$monitor = new PerformanceMonitor();
$monitor->startTimer('seo_analysis');
$analysis = $seoManager->analyze($content);
$metrics = $monitor->endTimer('seo_analysis');
error_log("SEO Analysis took {$metrics['execution_time']} seconds");// Using Laravel's built-in monitoring
use Illuminate\Support\Facades\Log;
class SeoController extends Controller
{
public function analyze(Request $request)
{
$startTime = microtime(true);
$startMemory = memory_get_usage();
$analysis = app(SeoManager::class)->analyze($request->input('content'));
$executionTime = microtime(true) - $startTime;
$memoryUsage = memory_get_usage() - $startMemory;
Log::info('SEO Analysis Performance', [
'execution_time' => $executionTime,
'memory_usage' => $memoryUsage,
'content_length' => strlen($request->input('content'))
]);
return response()->json($analysis->toArray());
}
}- Use Caching: Always implement caching for repeated operations
- Limit Content Size: Set reasonable limits for content analysis
- Profile Regularly: Monitor performance in production
- Optimize Queries: Use efficient database queries for SEO data
- Memory Management: Clean up resources after processing
// Use Laravel's cache tags for better cache management
Cache::tags(['seo', 'content'])->put($cacheKey, $analysis, 3600);
// Clear related caches when content changes
Cache::tags(['seo'])->flush();// Use Symfony's cache pools for different cache types
$seoCache = $cachePool->getItem('seo.' . $cacheKey);
$structuredDataCache = $cachePool->getItem('structured_data.' . $cacheKey);- Enable OPcache: Use PHP OPcache for better performance
- Use CDN: Serve static assets from CDN
- Database Optimization: Optimize database queries and indexes
- Load Balancing: Distribute load across multiple servers
- Monitoring: Set up application performance monitoring
-
Slow Analysis: Large content or complex HTML structure
- Solution: Implement content chunking or lazy loading
-
High Memory Usage: Processing large amounts of data
- Solution: Use streaming processing or memory limits
-
Cache Misses: Poor cache hit ratio
- Solution: Optimize cache keys and TTL settings
-
Database Bottlenecks: Slow SEO data queries
- Solution: Add indexes and optimize queries
Performance optimization is crucial for maintaining fast response times while providing comprehensive SEO analysis. Regular monitoring and profiling will help identify bottlenecks and optimization opportunities.