mirror of https://github.com/buster-so/buster.git
Update redirect for 307
This commit is contained in:
parent
7fa25cb761
commit
8552254dbf
|
@ -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/)
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -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: () => {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ export const Route = createFileRoute('/app/_app/datasets/$datasetId')({
|
|||
to: '/app/datasets/$datasetId/overview',
|
||||
params,
|
||||
replace: true,
|
||||
statusCode: 301,
|
||||
statusCode: 307,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ export const Route = createFileRoute(
|
|||
to: '/app/settings/dataset-groups/$datasetGroupId/datasets',
|
||||
params,
|
||||
replace: true,
|
||||
statusCode: 301,
|
||||
statusCode: 307,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ export const Route = createFileRoute('/app/_settings/settings/')({
|
|||
throw redirect({
|
||||
to: '/app/settings/profile',
|
||||
replace: true,
|
||||
statusCode: 301,
|
||||
statusCode: 307,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue