6.6 KiB
Production Push Polish Template
Use when: Preparing a codebase for production deployment after feature development is complete.
Purpose: Ensure all routes work correctly, render meaningful content, handle errors gracefully, and meet security/performance standards.
How to Request This Task
When asking an agent to create a prod push polish task, include:
Create a production push polish task following the template at:
resources/plan-templates/prod-push-polish.md
Focus areas: [list any specific concerns]
Target deployment date: [date if applicable]
Task Structure
Phase 1: Public Route Tests
Every public route must have a test that:
- Asserts HTTP 200 OK status
- Asserts meaningful HTML content renders (title, headings, key elements)
- Does NOT just use
assertOk()alone
Pattern:
it('renders [page name] with [key content]', function () {
$this->get('/route')
->assertOk()
->assertSee('Expected heading')
->assertSee('Expected content')
->assertSee('Expected CTA');
});
Why: A page can return 200 with blank body, PHP errors, or broken layout. Content assertions catch these.
Phase 2: Authenticated Route Tests
Every authenticated route must have a test that:
- Uses
actingAs()with appropriate user type - Asserts the Livewire component renders
- Asserts key UI elements are present
Pattern:
it('renders [page name] for authenticated user', function () {
$this->actingAs($this->user)
->get('/hub/route')
->assertOk()
->assertSeeLivewire('component.name')
->assertSee('Expected heading')
->assertSee('Expected widget');
});
Phase 3: Error Page Verification
Error pages must:
- Use consistent brand styling (not default Laravel)
- Provide helpful messages
- Include navigation back to safe pages
- Not expose stack traces in production
Test pattern:
it('renders 404 with helpful message', function () {
$this->get('/nonexistent-route')
->assertNotFound()
->assertSee('Page not found')
->assertSee('Go to homepage')
->assertDontSee('Exception');
});
Phase 4: Security Headers
Verify these headers are present on all responses:
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options |
DENY or SAMEORIGIN |
Prevent clickjacking |
X-Content-Type-Options |
nosniff |
Prevent MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Control referrer info |
Content-Security-Policy |
(varies) | Prevent XSS |
X-Powered-By |
(removed) | Don't expose stack |
Middleware pattern:
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->remove('X-Powered-By');
return $response;
}
}
Phase 5: Performance Baseline
Document response times for key routes:
| Route Type | Target | Acceptable | Needs Investigation |
|---|---|---|---|
| Static marketing | <200ms | <400ms | >600ms |
| Dynamic public | <300ms | <500ms | >800ms |
| Authenticated dashboard | <500ms | <800ms | >1200ms |
| Data-heavy pages | <800ms | <1200ms | >2000ms |
Test pattern:
it('responds within performance target', function () {
$start = microtime(true);
$this->get('/');
$duration = (microtime(true) - $start) * 1000;
expect($duration)->toBeLessThan(400); // ms
});
N+1 detection:
it('has no N+1 queries on listing page', function () {
DB::enableQueryLog();
$this->get('/hub/social/posts');
$queries = DB::getQueryLog();
// With 10 posts, should be ~3 queries (posts, accounts, user)
// not 10+ (one per post)
expect(count($queries))->toBeLessThan(10);
});
Phase 6: Final Verification
Pre-deployment checklist:
./vendor/bin/pest— 0 failuresnpm run build— 0 errorsnpm run test:smoke— Playwright passes- Error pages reviewed manually
- Security headers verified via browser dev tools
- Performance baselines documented
Acceptance Criteria Template
Copy and customise for your task:
### Phase 1: Public Route Tests
- [ ] AC1: Test for `/` asserts page title and hero content
- [ ] AC2: Test for `/pricing` asserts pricing tiers display
- [ ] AC3: Test for `/login` asserts form fields render
[Add one AC per route]
### Phase 2: Authenticated Route Tests
- [ ] AC4: Test for `/hub` asserts dashboard widgets render
- [ ] AC5: Test for `/hub/profile` asserts form with user data
[Add one AC per authenticated route]
### Phase 3: Error Page Verification
- [ ] AC6: 404 page renders with brand styling
- [ ] AC7: 403 page renders with access denied message
- [ ] AC8: 500 page renders without stack trace
### Phase 4: Security Headers
- [ ] AC9: X-Frame-Options header present
- [ ] AC10: X-Content-Type-Options header present
- [ ] AC11: Referrer-Policy header present
- [ ] AC12: X-Powered-By header removed
### Phase 5: Performance Baseline
- [ ] AC13: Homepage <400ms response time
- [ ] AC14: No N+1 queries on listing pages
- [ ] AC15: Performance baselines documented
### Phase 6: Final Verification
- [ ] AC16: Full test suite passes
- [ ] AC17: Build completes without errors
- [ ] AC18: Smoke tests pass
Common Issues to Check
Blank Pages
- Missing
returnin controller - Livewire component
render()returns nothing - Blade
@extendspointing to missing layout
Broken Layouts
- Missing Flux UI components
- CSS not loaded (Vite build issue)
- Alpine.js not initialised
Authentication Redirects
- Middleware order incorrect
- Session not persisting
- CSRF token mismatch
Performance Problems
- Eager loading missing (N+1)
- Large datasets not paginated
- Expensive queries in loops
- Missing database indexes
Security Gaps
- Debug mode enabled in production
- Sensitive data in logs
- Missing CSRF protection
- Exposed .env or config values
File Locations
| Purpose | Location |
|---|---|
| Route tests | tests/Feature/PublicRoutesTest.php, tests/Feature/HubRoutesTest.php |
| Error pages | resources/views/errors/ |
| Security middleware | app/Http/Middleware/SecurityHeaders.php |
| Performance tests | tests/Feature/PerformanceBaselineTest.php |
| Smoke tests | tests/Browser/smoke/ |
This template ensures production deployments don't ship broken pages.