Files
rspade_system/app/RSpade/man/ssr_fpc.txt
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
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>
2025-10-21 02:08:33 +00:00

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
================================================================================