Framework updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-03-12 19:09:07 +00:00
parent 3294fc7337
commit daa9bb2fb1
47 changed files with 2495 additions and 525 deletions

258
app/RSpade/man/fpc.txt Executable file
View File

@@ -0,0 +1,258 @@
FPC(3) RSX Framework Manual FPC(3)
NAME
FPC - Full Page Cache via Node.js reverse proxy with Redis
SYNOPSIS
Controller attribute:
#[FPC]
#[Route('/about', methods: ['GET'])]
public static function index(Request $request, array $params = [])
PHP cache management:
use App\RSpade\Core\FPC\Rsx_FPC;
Rsx_FPC::clear(); // Clear all FPC entries
Rsx_FPC::clear_url('/about'); // Clear specific URL
DESCRIPTION
The Full Page Cache (FPC) is a Node.js reverse proxy that sits between
nginx and PHP. Pages marked with #[FPC] are cached in Redis after first
render. Subsequent requests for the same URL are served directly from
Redis without touching PHP, with ETag validation for 304 responses.
Unlike traditional PHP caching (which still boots Laravel on every
request), the FPC proxy is a long-running Node.js process that serves
cached responses in under 1ms. PHP is only invoked on cache misses.
FPC is designed for public, anonymous pages like marketing sites,
documentation, and SSR-rendered content. Pages accessed by logged-in
users always bypass the cache.
Architecture:
browser -> nginx ->
/_ajax/ -> ajax FPM pool (unchanged)
static files -> try_files (unchanged)
everything else -> FPC proxy (port 3200) ->
cache HIT -> Redis -> 304 or cached HTML
cache MISS -> nginx backend (port 3201) -> FPM ->
if X-RSpade-FPC header -> cache in Redis
ATTRIBUTE USAGE
Add #[FPC] to any controller method that should be cached:
#[FPC]
#[Route('/pricing', methods: ['GET'])]
public static function pricing(Request $request, array $params = [])
{
return rsx_view('Pricing_Page');
}
FPC Constraints:
- Requires #[Route] attribute (enforced by manifest validation)
- Cannot combine with #[Ajax_Endpoint]
- Cannot combine with #[SPA]
- Cannot combine with #[Task]
The #[FPC] attribute only affects GET requests from anonymous visitors.
For logged-in users, POST requests, or non-GET methods, the request
passes through to PHP normally with no caching.
CACHE BEHAVIOR
Cache Key:
fpc:{build_key}:{sha1(path?sorted_query_params)}
The build key is the manifest hash, written to
system/storage/rsx-build/build_key during manifest save. When any file
changes trigger a manifest rebuild, the build key changes and all FPC
entries are effectively invalidated (new cache keys, old entries expire
via Redis LRU).
Cache Entry (stored as JSON in Redis):
url - Request path
html - Full HTML response body
status_code - HTTP status (200, 301, 302, etc.)
content_type - Response Content-Type header
location - Redirect target (for 3xx responses)
etag - SHA1 hash of body (first 30 chars)
cached_at - ISO timestamp of cache time
ETag Validation:
Client sends If-None-Match header with cached ETag.
If ETag matches cached entry, proxy returns 304 Not Modified
with no body. This saves bandwidth on repeat visits.
Session Cookie Bypass:
If the request contains an 'rsx' cookie (indicating the visitor
has an active session), the proxy passes through directly to PHP.
Session users never receive cached content.
POST/Non-GET Bypass:
Non-GET/HEAD methods always pass through to PHP.
HEAD requests can READ from cache but never POPULATE it.
RESPONSE HEADERS
X-FPC-Cache: HIT - Response served from Redis cache
X-FPC-Cache: MISS - Response served from PHP (and cached for next time)
ETag: {hash} - Content hash for 304 validation
The internal X-RSpade-FPC header (set by PHP to signal cacheability)
is stripped by the proxy and never sent to clients.
PHP CACHE MANAGEMENT
Rsx_FPC::clear()
Clear all FPC entries for the current build key.
Uses SCAN (not KEYS) to avoid blocking Redis.
Returns count of deleted entries.
Rsx_FPC::clear_url(string $url)
Clear FPC entry for a specific URL path.
Handles query parameter sorting to match proxy key generation.
Returns true if entry was deleted.
Examples:
// Clear all cached pages after content update
Rsx_FPC::clear();
// Clear specific page after editing its content
Rsx_FPC::clear_url('/about');
Rsx_FPC::clear_url('/search?q=test&page=1');
CONFIGURATION
Environment variables (in .env):
FPC_PROXY_PORT Port for FPC proxy (default: 3200)
FPC_BACKEND_PORT Port for nginx backend (default: 3201)
REDIS_HOST Redis server host (default: 127.0.0.1)
REDIS_PORT Redis server port (default: 6379)
REDIS_PASSWORD Redis password (default: none)
Redis uses DB 0 (default database with LRU eviction policy).
NGINX SETUP
The FPC requires two nginx changes:
1. Backend server block (accessed by proxy for cache misses):
server {
listen 127.0.0.1:3201;
root /var/www/html/system/public;
location /_ajax/ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_pass unix:/run/php/php-fpm-ajax.sock;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}
2. Main server blocks - replace location / with proxy fallback:
location / {
try_files $uri $uri/ @fpc_proxy;
}
location @fpc_proxy {
proxy_pass http://127.0.0.1:3200;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
SUPERVISOR
The FPC proxy runs as a supervised service:
/system/supervisor/fpc-proxy.conf
Start manually for testing:
node system/bin/fpc-proxy.js
DISPATCHER BEHAVIOR
When the Dispatcher matches a route with #[FPC]:
1. Checks request is GET with no POST/FILES data
2. Calls Session::init() to load existing session (does NOT create one)
3. If user is logged in, skips FPC (no header set)
4. If anonymous, blanks all cookies except 'rsx' from $_COOKIE
5. Executes controller method normally
6. Adds X-RSpade-FPC: 1 header to response
7. Removes Set-Cookie headers from response
Cookie blanking (step 4) ensures controller code produces identical
output for all anonymous visitors regardless of any tracking cookies.
SESSION COOKIE SAFETY
RSpade does NOT create session cookies for anonymous visitors.
Session::init() only reads existing cookies, never creates new ones.
Session::set_site_id() stores site_id as a request-scoped override
for anonymous visitors without creating a database session.
Sessions are only created when code explicitly calls:
- Session::get_session_id()
- Session::get_session()
- Session::set_login_user_id()
This ensures FPC-cacheable pages never produce Set-Cookie headers
that would prevent caching.
DEBUGGING
Check FPC cache status:
curl -sI http://localhost:3200/your-page | grep X-FPC
View cached entry:
redis-cli GET "fpc:{build_key}:{sha1_of_url}"
List all FPC keys:
redis-cli KEYS "fpc:*"
Clear all FPC entries:
redis-cli KEYS "fpc:*" | xargs -r redis-cli DEL
Check proxy logs:
/var/log/supervisor/fpc-proxy.log
/var/log/supervisor/fpc-proxy-error.log
TESTING
Integration tests in /system/app/RSpade/tests/fpc/:
01_session_cookie_behavior - Verifies no cookies for anonymous
02_fpc_cache_behavior - Cache MISS/HIT, ETag 304, bypasses
Run tests:
./system/app/RSpade/tests/fpc/01_session_cookie_behavior/run_test.sh
./system/app/RSpade/tests/fpc/02_fpc_cache_behavior/run_test.sh
FILES
system/bin/fpc-proxy.js Node.js proxy server
system/app/RSpade/Core/FPC/Rsx_FPC.php PHP cache utility
system/supervisor/fpc-proxy.conf Supervisor config
system/storage/rsx-build/build_key Build key file
system/app/RSpade/tests/fpc/ Integration tests
SEE ALSO
ssr(3) - Server-side rendering (produces HTML that FPC caches)
session(3) - Session management and cookie behavior

204
app/RSpade/man/ssr.txt Executable file
View File

@@ -0,0 +1,204 @@
SSR(3) RSX Framework Manual SSR(3)
NAME
SSR - Server-side rendering of jqhtml components via Node.js
SYNOPSIS
use App\RSpade\Core\SSR\Rsx_SSR;
$result = Rsx_SSR::render_component('Component_Name', $args, 'Bundle_Class');
// $result = ['html' => '...', 'timing' => [...], 'preload' => [...]]
DESCRIPTION
Rsx_SSR renders jqhtml components to HTML strings on the server using a
long-running Node.js process. The rendered HTML is injected into a Blade
template so the browser receives fully-formed markup without waiting for
JavaScript execution.
Primary use case: search engine crawlers and social media link previews.
Crawlers that don't execute JavaScript see empty SPA shells. SSR provides
real HTML content for indexing while the same components work as normal
client-side jqhtml components for regular users.
SSR pages are for unauthenticated users only. The SSR Node environment
has no session, no cookies, and no user context. Authenticated users
should be served the normal SPA/CSR version of the page.
HOW IT WORKS
1. PHP controller calls Rsx_SSR::render_component()
2. PHP acquires a Redis advisory lock (LOCK_SSR_RENDER) to serialize
SSR operations across concurrent requests
3. PHP ensures the SSR Node server is running (auto-starts if needed)
4. PHP reads the compiled bundle files and sends them + the component
name to the Node server over a Unix socket
5. Node creates an isolated jsdom environment, executes the bundles,
runs the full RSpade 10-phase boot lifecycle (with SSR guards on
browser-only classes), then renders the component
6. The component's on_load() runs normally, making Ajax calls back to
PHP to fetch data (e.g., Controller.get_page_data())
7. Node returns the rendered HTML and any preload data
8. PHP injects the HTML into a Blade template and optionally seeds
preload data via PageData for client-side hydration
The Node server stays running across requests and is automatically
restarted when the manifest build key changes (indicating code changes).
CONTROLLER USAGE
A typical SSR controller has three parts: the SSR route, a CSR fallback
route, and a public Ajax endpoint for data.
1. SSR Route (returns pre-rendered HTML):
use App\RSpade\Core\SSR\Rsx_SSR;
use App\RSpade\Core\View\PageData;
#[Route('/pricing', methods: ['GET'])]
public static function index(Request $request, array $params = [])
{
$result = Rsx_SSR::render_component(
'Pricing_Page', // Component class name
[], // Component args (becomes this.args)
'Marketing_Bundle' // Bundle containing the component
);
// Seed preload data so client-side hydration skips on_load()
if (!empty($result['preload'])) {
PageData::add([
'ssr' => true,
'ssr_preload' => $result['preload'],
]);
}
return rsx_view('Pricing_Index', [
'html' => $result['html'],
]);
}
2. Blade Template (injects SSR HTML into the page):
The Blade template outputs {!! $html !!} inside the container where
the component would normally render client-side.
3. Public Ajax Endpoint (serves data to both SSR and CSR):
#[Ajax_Endpoint]
public static function get_page_data(Request $request, array $params = [])
{
return [
'plans' => Plan_Model::where('active', true)->get()->toArray(),
'features' => Feature_Model::all()->toArray(),
];
}
The component's on_load() calls this endpoint identically whether
running server-side in Node or client-side in the browser:
async on_load() {
const data = await Marketing_Controller.get_page_data();
this.data.plans = data.plans;
this.data.features = data.features;
}
COMPONENT RESTRICTIONS
Components rendered via SSR must follow these rules:
- Data loading goes in on_load(), not on_create()
on_create() should only set empty defaults (this.data.items = []).
on_load() fetches data via Ajax endpoints. This is required because
SSR captures the data returned by on_load() for the preload cache.
- Ajax endpoints must be public (@auth-exempt on the controller)
The SSR Node server has no session. Ajax calls from SSR go through
PHP as unauthenticated requests.
- No browser-only APIs in on_create() or on_load()
window.innerWidth, navigator, localStorage (the real ones), etc.
are not available. on_ready() is fine since it only runs client-side.
- No user-specific content
SSR pages are cached and served to all unauthenticated users.
User-specific content belongs in authenticated SPA routes.
SERVER LIFECYCLE
The SSR Node server is managed automatically by Rsx_SSR:
- Auto-start: First render_component() call starts the server
- Persistence: Server stays running across PHP requests
- Auto-restart: Server restarts when manifest build key changes
- Concurrency: Redis advisory lock serializes SSR operations
- Cleanup: Server is killed by PID when restarting
Files:
storage/rsx-tmp/ssr-server.sock Unix socket
storage/rsx-tmp/ssr-server.pid Process ID
storage/rsx-tmp/ssr-server.build_key Last known build key
The server process is daemonized (detached from PHP). It survives
PHP-FPM worker recycling and terminal disconnects.
NGINX AND PHP-FPM DEADLOCK PREVENTION
SSR components that call Ajax endpoints create a callback loop:
Browser -> nginx -> PHP-FPM (page request)
|
v
Node SSR server
|
v (Ajax callback)
nginx -> PHP-FPM (ajax request)
If both the page request and the Ajax callback compete for the same
PHP-FPM worker pool, a deadlock occurs when all workers are busy
handling SSR page requests, leaving no workers for Ajax callbacks.
Solution: Route Ajax requests to a separate PHP-FPM pool.
PHP-FPM Configuration:
/etc/php/8.4/fpm/pool.d/www.conf (page requests):
[www]
listen = /run/php/php-fpm.sock
pm = ondemand
pm.max_children = 4
/etc/php/8.4/fpm/pool.d/ajax.conf (Ajax requests):
[ajax]
listen = /run/php/php-fpm-ajax.sock
pm = ondemand
pm.max_children = 4
Nginx Configuration (add to each server block):
# Ajax requests use separate FPM pool (prevents SSR deadlock)
location /_ajax/ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_pass unix:/run/php/php-fpm-ajax.sock;
}
For HTTPS server blocks, also add:
fastcgi_param HTTPS 'on';
This ensures SSR Ajax callbacks always have available workers,
regardless of how many page requests are in-flight.
PERFORMANCE
First request after server start: ~800-1000ms (server startup + render)
Subsequent requests (server warm): ~150-300ms (render only)
Bundle code is cached in-memory by the Node server across requests.
For high-traffic SSR pages, combine with the Full Page Cache system
(see rsx:man ssr_fpc) to serve pre-rendered HTML from Redis without
invoking the SSR server at all.
SEE ALSO
rsx:man ssr_fpc Full Page Cache for static SSR pages
rsx:man pagedata Server-to-client data passing
rsx:man jqhtml Component system
rsx:man ajax_error_handling Ajax endpoint patterns
AUTHOR
RSpade Framework
RSpade March 2026 SSR(3)

View File

@@ -1,359 +0,0 @@
================================================================================
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
================================================================================