Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
360 lines
11 KiB
Plaintext
Executable File
360 lines
11 KiB
Plaintext
Executable File
================================================================================
|
|
SSR FULL PAGE CACHE (FPC)
|
|
================================================================================
|
|
|
|
OVERVIEW
|
|
|
|
The SSR (Server-Side Rendered) Full Page Cache system pre-renders static pages
|
|
via headless Chrome (Playwright) and caches the resulting HTML in Redis for
|
|
optimal SEO and performance. Routes marked with #[Static_Page] attribute are
|
|
automatically cached and served to unauthenticated users.
|
|
|
|
This system is ideal for:
|
|
- Public-facing pages (landing pages, about, blog posts, documentation)
|
|
- Content that changes infrequently but is accessed frequently
|
|
- Pages requiring optimal SEO (search engines get pre-rendered HTML)
|
|
- Reducing server load for high-traffic public pages
|
|
|
|
|
|
CORE CONCEPTS
|
|
|
|
1. STATIC PAGE MARKING
|
|
Routes opt-in to FPC by adding the #[Static_Page] attribute to their
|
|
controller method. Only routes with this attribute are eligible for caching.
|
|
|
|
2. AUTHENTICATION-AWARE CACHING
|
|
FPC only serves cached content to unauthenticated users (no active session).
|
|
Logged-in users always get dynamic content, ensuring personalized experiences.
|
|
|
|
3. BUILD-KEY AUTO-INVALIDATION
|
|
Cache keys incorporate the application's build_key, ensuring all cached
|
|
pages are automatically invalidated on deployment. No manual cache clearing
|
|
needed during deployments.
|
|
|
|
4. ETAG VALIDATION
|
|
Each cached page includes a 30-character ETag for HTTP cache validation.
|
|
Browsers can send If-None-Match headers to receive 304 Not Modified
|
|
responses, reducing bandwidth usage.
|
|
|
|
5. REDIRECT CACHING
|
|
If a route redirects (300-399 status codes), the FPC system caches the
|
|
first redirect response without following it, preserving redirect behavior.
|
|
|
|
6. QUERY PARAMETER STRIPPING
|
|
GET parameters are stripped from URLs before cache key generation, ensuring
|
|
/about and /about?utm_source=email serve the same cached content.
|
|
|
|
|
|
ATTRIBUTE SYNTAX
|
|
|
|
Add the #[Static_Page] attribute to any route method:
|
|
|
|
#[Static_Page]
|
|
#[Route('/about')]
|
|
public static function about(Request $request, array $params = [])
|
|
{
|
|
return view('frontend.about');
|
|
}
|
|
|
|
Requirements:
|
|
- Route must be GET only (POST, PUT, DELETE not supported)
|
|
- Route must not require authentication (served to unauthenticated users only)
|
|
- Route should produce consistent output regardless of GET parameters
|
|
|
|
|
|
CACHE LIFECYCLE
|
|
|
|
1. ROUTE DISCOVERY
|
|
During request dispatch, the Dispatcher checks if the matched route has
|
|
the #[Static_Page] attribute.
|
|
|
|
2. SESSION CHECK
|
|
If the user has an active session, FPC is bypassed and the route executes
|
|
normally to provide personalized content.
|
|
|
|
3. CACHE LOOKUP
|
|
For unauthenticated users, the Dispatcher queries Redis for a cached version
|
|
using the key format: ssr_fpc:{build_key}:{sha1(url)}
|
|
|
|
4. CACHE GENERATION (ON MISS)
|
|
If no cache exists, the system automatically runs:
|
|
php artisan rsx:ssr_fpc:create /route
|
|
|
|
This command:
|
|
- Launches headless Chrome via Playwright
|
|
- Sets X-RSpade-FPC-Client header to prevent infinite loops
|
|
- Navigates to the route and waits for _debug_ready event
|
|
- Captures the fully rendered DOM or redirect response
|
|
- Stores the result in Redis with metadata (ETag, build_key, etc.)
|
|
|
|
5. CACHE SERVING
|
|
The cached HTML is served with appropriate headers:
|
|
- ETag: {30-char hash} for cache validation
|
|
- Cache-Control: public, max-age=300 (5 min in production)
|
|
- Cache-Control: no-cache, must-revalidate (0s in development)
|
|
|
|
6. CACHE INVALIDATION
|
|
Caches are automatically invalidated when:
|
|
- Application is deployed (build_key changes)
|
|
- Redis LRU eviction policy removes old entries
|
|
- Manual reset via rsx:ssr_fpc:reset command
|
|
|
|
|
|
COMMANDS
|
|
|
|
php artisan rsx:ssr_fpc:create <url>
|
|
Generate static cache for a specific URL.
|
|
|
|
Arguments:
|
|
url The URL path to cache (e.g., /about)
|
|
|
|
Behavior:
|
|
- Only available in non-production environments
|
|
- Requires rsx.ssr_fpc.enabled = true
|
|
- Strips GET parameters from URL
|
|
- Launches Playwright to render page
|
|
- Stores result in Redis
|
|
- Runs under GENERATE_STATIC_CACHE exclusive lock
|
|
|
|
Examples:
|
|
php artisan rsx:ssr_fpc:create /
|
|
php artisan rsx:ssr_fpc:create /about
|
|
php artisan rsx:ssr_fpc:create /blog/post-title
|
|
|
|
php artisan rsx:ssr_fpc:reset
|
|
Clear all SSR FPC cache entries from Redis.
|
|
|
|
Behavior:
|
|
- Available in all environments (local, staging, production)
|
|
- Safe to run - only affects ssr_fpc:* keys
|
|
- Does NOT affect application caches, sessions, or queue jobs
|
|
- Reports count of cleared cache entries
|
|
|
|
When to use:
|
|
- After major content updates across the site
|
|
- When troubleshooting caching issues
|
|
- Before deployment to ensure fresh cache generation (optional)
|
|
- When build_key changes (auto-invalidation should handle this)
|
|
|
|
|
|
CONFIGURATION
|
|
|
|
Configuration is stored in config/rsx.php under the 'ssr_fpc' key:
|
|
|
|
'ssr_fpc' => [
|
|
// Enable SSR Full Page Cache system
|
|
'enabled' => env('SSR_FPC_ENABLED', false),
|
|
|
|
// Playwright generation timeout in milliseconds
|
|
'generation_timeout' => env('SSR_FPC_TIMEOUT', 30000),
|
|
],
|
|
|
|
Environment Variables:
|
|
SSR_FPC_ENABLED Enable/disable the FPC system (default: false)
|
|
SSR_FPC_TIMEOUT Page generation timeout in ms (default: 30000)
|
|
|
|
To enable FPC, add to .env:
|
|
SSR_FPC_ENABLED=true
|
|
|
|
|
|
REDIS CACHE STRUCTURE
|
|
|
|
Cache Key Format:
|
|
ssr_fpc:{build_key}:{sha1(url)}
|
|
|
|
Example:
|
|
ssr_fpc:abc123def456:5d41402abc4b2a76b9719d911017c592
|
|
|
|
Cache Value (JSON):
|
|
{
|
|
"url": "/about",
|
|
"code": 200,
|
|
"page_dom": "<!DOCTYPE html>...",
|
|
"redirect": null,
|
|
"build_key": "abc123def456",
|
|
"etag": "abc123def456789012345678901234",
|
|
"generated_at": "2025-10-17 14:32:10"
|
|
}
|
|
|
|
For redirect responses:
|
|
{
|
|
"url": "/old-page",
|
|
"code": 301,
|
|
"page_dom": null,
|
|
"redirect": "/new-page",
|
|
"build_key": "abc123def456",
|
|
"etag": "abc123def456789012345678901234",
|
|
"generated_at": "2025-10-17 14:32:10"
|
|
}
|
|
|
|
Cache Eviction:
|
|
Redis LRU (Least Recently Used) policy automatically removes old entries
|
|
when memory limits are reached. No manual TTL management required.
|
|
|
|
|
|
SECURITY CONSIDERATIONS
|
|
|
|
1. INFINITE LOOP PREVENTION
|
|
The Playwright script sets X-RSpade-FPC-Client: 1 header to identify itself
|
|
as a cache generation request. Session::__is_fpc_client() checks this header
|
|
to prevent FPC from serving cached content to the generation process.
|
|
|
|
2. PRODUCTION COMMAND BLOCKING
|
|
The rsx:ssr_fpc:create command throws a fatal error in production to prevent
|
|
accidental cache generation in production environments. Cache generation
|
|
should happen automatically on first request or via staging/local environments.
|
|
|
|
3. AUTHENTICATION BYPASS
|
|
FPC only serves to unauthenticated users. Auth checks still run for logged-in
|
|
users, ensuring secure routes remain protected even if accidentally marked
|
|
with #[Static_Page].
|
|
|
|
4. QUERY PARAMETER STRIPPING
|
|
GET parameters are stripped before caching to prevent cache poisoning via
|
|
malicious query strings. Routes that depend on GET parameters should NOT
|
|
use #[Static_Page].
|
|
|
|
|
|
PERFORMANCE CHARACTERISTICS
|
|
|
|
Cache Hit (Unauthenticated User):
|
|
- Served directly from Redis before route execution
|
|
- No PHP controller execution
|
|
- No database queries
|
|
- Minimal memory usage
|
|
- Response time: ~1-5ms
|
|
|
|
Cache Miss (First Request):
|
|
- Playwright launches headless Chrome
|
|
- Page renders fully (waits for _debug_ready + network idle)
|
|
- DOM captured and stored in Redis
|
|
- Response time: ~500-2000ms (one-time cost)
|
|
|
|
Authenticated User:
|
|
- FPC bypassed completely
|
|
- Normal route execution
|
|
- No performance impact
|
|
|
|
|
|
DEBUGGING AND TROUBLESHOOTING
|
|
|
|
Enable console_debug output for FPC:
|
|
CONSOLE_DEBUG_FILTER=SSR_FPC php artisan rsx:debug /about
|
|
|
|
Common issues:
|
|
|
|
1. Cache not being served
|
|
- Check: rsx.ssr_fpc.enabled = true
|
|
- Check: Route has #[Static_Page] attribute
|
|
- Check: User is unauthenticated (no active session)
|
|
- Check: Redis is running and accessible
|
|
|
|
2. Cache generation fails
|
|
- Check: Playwright is installed (npm install)
|
|
- Check: Timeout setting (increase SSR_FPC_TIMEOUT)
|
|
- Check: Route returns valid HTML (not JSON/PDF/etc.)
|
|
- Check: Route doesn't redirect to external domains
|
|
|
|
3. Stale cache after deployment
|
|
- Cache should auto-invalidate (build_key changed)
|
|
- Manual reset: php artisan rsx:ssr_fpc:reset
|
|
- Check: build_key is being regenerated on deployment
|
|
|
|
4. 304 responses not working
|
|
- Check: Browser sends If-None-Match header
|
|
- Check: ETag matches exactly (case-sensitive)
|
|
- Check: Response includes ETag header
|
|
|
|
|
|
FUTURE ROADMAP
|
|
|
|
Planned enhancements:
|
|
|
|
1. SITEMAP INTEGRATION
|
|
Automatically generate static caches for all routes in sitemap.xml during
|
|
deployment. Ensures all public pages are pre-cached before first user visit.
|
|
|
|
2. PARALLEL GENERATION
|
|
Generate multiple static caches concurrently using a worker pool, reducing
|
|
total cache generation time for large sites.
|
|
|
|
3. SHARED SECRET KEYS
|
|
Support deployment across multiple servers with shared cache keys, enabling
|
|
cache pre-generation on staging before production deployment.
|
|
|
|
4. EXTERNAL CACHE SERVICE
|
|
Move cache generation to a separate service (e.g., AWS Lambda, dedicated
|
|
worker) to reduce load on application servers during cache regeneration.
|
|
|
|
5. PROGRAMMATIC CACHE RESET
|
|
Add programmatic methods to clear specific route caches after content updates:
|
|
Rsx_Static_Cache::clear('/blog/post-slug')
|
|
Rsx_Static_Cache::clear_pattern('/blog/*')
|
|
|
|
|
|
RELATED DOCUMENTATION
|
|
|
|
- php artisan rsx:man routing (Route system and attributes)
|
|
- php artisan rsx:man caching (General caching concepts)
|
|
- php artisan rsx:man console_debug (Debug output configuration)
|
|
- php artisan rsx:man auth (Authentication system)
|
|
|
|
|
|
EXAMPLES
|
|
|
|
Basic static page:
|
|
|
|
#[Static_Page]
|
|
#[Route('/')]
|
|
#[Auth('Permission::anybody()')]
|
|
public static function index(Request $request, array $params = [])
|
|
{
|
|
return view('frontend.index', [
|
|
'featured_products' => Product_Model::where('featured', true)->get(),
|
|
]);
|
|
}
|
|
|
|
Static page with redirect:
|
|
|
|
#[Static_Page]
|
|
#[Route('/old-page')]
|
|
#[Auth('Permission::anybody()')]
|
|
public static function old_page(Request $request, array $params = [])
|
|
{
|
|
return redirect('/new-page', 301);
|
|
}
|
|
|
|
NOT suitable for FPC (query-dependent):
|
|
|
|
// ❌ DO NOT use #[Static_Page] - depends on GET parameters
|
|
#[Route('/search')]
|
|
#[Auth('Permission::anybody()')]
|
|
public static function search(Request $request, array $params = [])
|
|
{
|
|
$query = $params['q'] ?? '';
|
|
return view('search.results', [
|
|
'results' => Product_Model::search($query)->get(),
|
|
]);
|
|
}
|
|
|
|
Pre-generate cache in deployment script:
|
|
|
|
#!/bin/bash
|
|
# deployment.sh
|
|
|
|
# Deploy application
|
|
git pull
|
|
composer install --no-dev --optimize-autoloader
|
|
php artisan migrate
|
|
|
|
# Pre-generate static caches for high-traffic pages
|
|
php artisan rsx:ssr_fpc:create /
|
|
php artisan rsx:ssr_fpc:create /about
|
|
php artisan rsx:ssr_fpc:create /pricing
|
|
php artisan rsx:ssr_fpc:create /contact
|
|
|
|
# Note: This is optional - caches will auto-generate on first request
|
|
|
|
|
|
================================================================================
|