Update redirect for 307

This commit is contained in:
Nate Kelley 2025-09-10 16:58:52 -06:00
parent 7fa25cb761
commit 8552254dbf
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
13 changed files with 137 additions and 158 deletions

View File

@ -0,0 +1,118 @@
# Browser Redirect Caching Issue - Fix Documentation
## ✅ The Real Problem: Browser Caching
Your issue wasn't with Cloudflare Workers or SSR - it was **browser caching of redirects**!
### Evidence:
- ✅ **Incognito mode works perfectly** (no cache)
- ❌ **Regular browser fails** (has cached redirects)
- ❌ **After deployment, old cached redirects persist**
## 🔍 Root Cause
1. **301 (Permanent) redirects are cached aggressively** by browsers
2. Browsers remember these redirects **even after deployments**
3. The cached redirects were causing the loading issues
## ✅ Solutions Applied
### 1. Changed All Status Codes from 301 to 307
**Why 307?**
- **301**: Permanent redirect (heavily cached)
- **302**: Temporary redirect (sometimes cached)
- **307**: Temporary redirect (NEVER cached)
**Files Updated:**
- `/src/routes/app/index.tsx` - Changed 301 → 307
- `/src/routes/app.tsx` - Changed 302 → 307
- `/src/routes/auth.logout.tsx` - Changed 301 → 307
- `/src/routes/app/_settings/settings.index.tsx` - Changed 301 → 307
- `/src/routes/app/_app/datasets.$datasetId.tsx` - Changed 301 → 307
- `/src/routes/app/_app/datasets.$datasetId.permissions.index.tsx` - Changed 301 → 307
- `/src/routes/app/_settings/_permissions/settings.dataset-groups.$datasetGroupId.index.tsx` - Changed 301 → 307
- `/src/routes/app/_settings/_restricted_layout/_admin_only.tsx` - Changed 301 → 307
- `/src/routes/app/_settings/_permissions.tsx` - Changed 301 → 307
### 2. Added Cache Control Headers
Modified `/src/middleware/global-security.ts` to add no-cache headers for redirect routes:
```typescript
if (isRedirectRoute) {
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
headers['Pragma'] = 'no-cache';
headers['Expires'] = '0';
}
```
### 3. Meta Refresh with Cache Prevention
Updated `/src/routes/index.tsx` with cache prevention meta tags:
```typescript
meta: [
{ 'http-equiv': 'refresh', content: '0; url=/app/home' },
{ 'http-equiv': 'Cache-Control', content: 'no-cache, no-store, must-revalidate' },
{ 'http-equiv': 'Pragma', content: 'no-cache' },
{ 'http-equiv': 'Expires', content: '0' },
]
```
## 🧹 Clear Your Browser Cache
**For existing users who might have cached 301 redirects:**
### Chrome/Edge:
1. Open DevTools (F12)
2. Right-click the refresh button
3. Select "Empty Cache and Hard Reload"
### Or Clear Specific Site Data:
1. Open DevTools → Application tab
2. Storage → Clear storage
3. Click "Clear site data"
### Firefox:
1. Ctrl+Shift+Delete
2. Select "Cache" only
3. Clear for "Last hour"
## 📊 Status Code Reference
| Code | Type | Cached? | Use Case |
|------|------|---------|----------|
| 301 | Permanent | ✅ Aggressively | Never for app redirects! |
| 302 | Temporary | ⚠️ Sometimes | Legacy, avoid |
| 303 | See Other | ❌ No | POST → GET redirect |
| 307 | Temporary | ❌ No | ✅ Best for app redirects |
| 308 | Permanent | ✅ Yes | Like 301 but preserves method |
## 🚀 Testing
1. **Clear browser cache first** (important!)
2. **Build and deploy:**
```bash
npm run build
npx wrangler deploy --env staging
```
3. **Test scenarios:**
- Visit `/` → Should redirect to `/app/home`
- Visit `/app/` → Should redirect to `/app/home`
- Log out → Should redirect to `/auth/login`
- All should work on first visit (cold start)
## 🎯 Key Takeaways
1. **Never use 301 for application redirects** - they're meant for permanent URL changes
2. **Use 307 for temporary redirects** that shouldn't be cached
3. **Browser caching can persist across deployments** and cause mysterious issues
4. **Incognito mode is your friend** for testing caching issues
## 🔮 Future Recommendations
1. **Consider server-side redirects** in Cloudflare configuration for static redirects
2. **Monitor with Chrome DevTools Network tab** - check "Disable cache" when debugging
3. **Use `wrangler tail` to see if requests are even hitting your worker**
## Related Issues
- [MDN: HTTP redirect status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages)
- [Chrome: Clear cache for specific site](https://support.google.com/chrome/answer/2392709)
- [Cloudflare: Page Rules for redirects](https://developers.cloudflare.com/rules/page-rules/)

View File

@ -1,132 +0,0 @@
# Cloudflare Workers Redirect Issue - Fix Documentation
## Problem Summary
After deploying to Cloudflare Workers, the application fails to load when accessing redirect URLs (like `/` or `/app/`) during cold starts. However, direct URLs work fine, and once loaded, all redirects work correctly.
## Root Cause
The issue stems from how Cloudflare Workers handles SSR (Server-Side Rendering) during cold starts:
1. JavaScript redirects thrown during SSR (`beforeLoad` hooks) don't execute properly
2. The SSR context on edge workers has limitations with redirect handling
3. Cookie-based authentication checks may not initialize properly during cold starts
## Solutions Applied
### Solution 1: Client-Only Redirects (Currently Implemented)
Modified routes to only perform redirects on the client-side:
**Files Modified:**
- `/src/routes/index.tsx`
- `/src/routes/app/index.tsx`
**Changes:**
```typescript
// Before (problematic)
beforeLoad: async () => {
throw redirect({ to: '/app/home', replace: true, statusCode: 302 });
}
// After (fixed)
beforeLoad: async () => {
if (!isServer) {
throw redirect({ to: '/app/home', replace: true });
}
},
loader: async () => {
if (isServer) {
return { shouldRedirect: true, redirectTo: '/app/home' };
}
return {};
},
component: () => {
const data = Route.useLoaderData();
React.useEffect(() => {
if (data?.shouldRedirect) {
window.location.href = data.redirectTo;
}
}, [data]);
return <div>Redirecting...</div>;
}
```
### Solution 2: Meta Refresh Fallback (Alternative)
If issues persist, use HTML meta refresh as a more reliable fallback:
```typescript
head: () => ({
meta: [
{ 'http-equiv': 'refresh', content: '0; url=/app/home' },
],
})
```
See `/src/routes/index.alternative.tsx` for full implementation.
## Additional Recommendations
### 1. Optimize Cold Starts
Add warming strategy in `wrangler.jsonc`:
```json
{
"triggers": {
"crons": ["*/5 * * * *"]
}
}
```
### 2. Consider Edge-Specific Routing
For production, consider using Cloudflare's native routing features:
```json
// wrangler.jsonc
{
"routes": [
{ "pattern": "/", "redirect": "/app/home" }
]
}
```
### 3. Monitor Performance
Enable Cloudflare Analytics to track cold start frequency:
```json
{
"observability": {
"enabled": true,
"head_sampling_rate": 1
}
}
```
## Testing the Fix
1. **Local Testing:**
```bash
npm run build
npx wrangler dev
```
2. **Staging Deployment:**
```bash
npx wrangler deploy --env staging
```
3. **Test Scenarios:**
- Cold start: Open incognito window, navigate to `/`
- Warm start: Navigate to `/` after loading any other page
- Direct access: Navigate directly to `/app/home`
- Authentication flow: Test login/logout redirects
## Rollback Plan
If issues persist after deployment:
1. Keep original files as `.backup`
2. Revert changes using git
3. Consider server-side HTTP redirects in Cloudflare configuration
## Related Issues
- TanStack Start SSR limitations: https://tanstack.com/router/latest/docs/framework/react/start/ssr
- Cloudflare Workers SSR: https://developers.cloudflare.com/workers/examples/render-react-app/
- Supabase SSR auth: https://supabase.com/docs/guides/auth/server-side-rendering
## Contact
If problems persist, consider:
1. Opening issue in TanStack Start repository
2. Cloudflare Workers Discord community
3. Implementing pure HTTP redirects at edge level

View File

@ -9,7 +9,6 @@ export const securityMiddleware = createMiddleware({ type: 'function' }).server(
const url = new URL(request.url);
const isEmbed = url.pathname.startsWith('/embed/');
// Set security headers BEFORE calling next() to ensure they're set only once
setHeaders(createSecurityHeaders(isEmbed));
const result = await next();

View File

@ -21,7 +21,7 @@ export const Route = createFileRoute('/app')({
if (isExpired || !accessToken) {
console.error('Access token is expired or not found');
throw redirect({ to: '/auth/login', replace: true, statusCode: 302 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
return {
@ -29,7 +29,7 @@ export const Route = createFileRoute('/app')({
};
} catch (error) {
console.error('Error in app route beforeLoad:', error);
throw redirect({ to: '/auth/login', replace: true, statusCode: 302 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
},
loader: async ({ context }) => {
@ -48,7 +48,7 @@ export const Route = createFileRoute('/app')({
if (!user) {
console.error('User not found - redirecting to login');
throw redirect({ to: '/auth/login', replace: true, statusCode: 302 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
return {
@ -60,7 +60,7 @@ export const Route = createFileRoute('/app')({
};
} catch (error) {
console.error('Error in app route loader:', error);
throw redirect({ to: '/auth/login', replace: true, statusCode: 302 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
},
component: () => {

View File

@ -6,7 +6,7 @@ export const Route = createFileRoute('/app/_app/datasets/$datasetId/permissions/
to: '/app/datasets/$datasetId/permissions/overview',
params,
replace: true,
statusCode: 301,
statusCode: 307,
});
},
});

View File

@ -13,7 +13,7 @@ export const Route = createFileRoute('/app/_app/datasets/$datasetId')({
to: '/app/datasets/$datasetId/overview',
params,
replace: true,
statusCode: 301,
statusCode: 307,
});
}
},

View File

@ -9,7 +9,7 @@ export const Route = createFileRoute('/app/_settings/_permissions')({
const { queryClient } = context;
const userData = await prefetchGetMyUserInfo(queryClient);
if (!userData || !userData.organizations || !checkIfUserIsAdmin(userData.organizations[0])) {
throw redirect({ to: '/auth/login', replace: true, statusCode: 301 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
},
});

View File

@ -8,7 +8,7 @@ export const Route = createFileRoute(
to: '/app/settings/dataset-groups/$datasetGroupId/datasets',
params,
replace: true,
statusCode: 301,
statusCode: 307,
});
},
});

View File

@ -7,7 +7,7 @@ export const Route = createFileRoute('/app/_settings/_restricted_layout/_admin_o
const { queryClient } = context;
const userData = await prefetchGetMyUserInfo(queryClient);
if (!userData || !userData.organizations || !checkIfUserIsAdmin(userData.organizations[0])) {
throw redirect({ to: '/auth/login', replace: true, statusCode: 301 });
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
}
},

View File

@ -5,7 +5,7 @@ export const Route = createFileRoute('/app/_settings/settings/')({
throw redirect({
to: '/app/settings/profile',
replace: true,
statusCode: 301,
statusCode: 307,
});
},
});

View File

@ -2,7 +2,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
export const Route = createFileRoute('/app/')({
beforeLoad: async () => {
throw redirect({ to: '/app/home', replace: true, statusCode: 301 });
throw redirect({ to: '/app/home', replace: true, statusCode: 307 });
},
component: () => null,
});

View File

@ -13,6 +13,6 @@ export const Route = createFileRoute('/auth/logout')({
preload: false,
loader: async () => {
await signOut();
throw redirect({ to: '/auth/login', statusCode: 301 });
throw redirect({ to: '/auth/login', statusCode: 307 });
},
});

View File

@ -4,7 +4,12 @@ import { isServer } from '@/lib/window';
export const Route = createFileRoute('/')({
head: () => ({
meta: [{ 'http-equiv': 'refresh', content: '0; url=/app/home' }],
meta: [
{ 'http-equiv': 'refresh', content: '0; url=/app/home' },
{ 'http-equiv': 'Cache-Control', content: 'no-cache, no-store, must-revalidate' },
{ 'http-equiv': 'Pragma', content: 'no-cache' },
{ 'http-equiv': 'Expires', content: '0' },
],
}),
beforeLoad: async () => {
// Only redirect on client-side to avoid SSR issues during cold starts
@ -19,16 +24,5 @@ export const Route = createFileRoute('/')({
}
return {};
},
component: () => {
const data = Route.useLoaderData();
// Client-side redirect after hydration
React.useEffect(() => {
if (data?.shouldRedirect) {
window.location.href = data.redirectTo;
}
}, [data]);
return <div>Redirecting...</div>;
},
component: () => null,
});