docs: add appendix for permanent path redirect fixes in cPanel deployment guide
Some checks failed
CI / skip-ci-check (pull_request) Successful in 31s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled

- Document long-term solutions for handling path prefixes in PunimTag deployments.
- Outline steps to configure viewer base path, align environment variables, and enforce consistent URL patterns.
- Include validation checklist and recommended deployment policy to ensure stable setups.
This commit is contained in:
Tanya 2026-03-23 14:11:44 -04:00
parent 064daf47f7
commit bda13fde67
2 changed files with 191 additions and 11 deletions

View File

@ -1150,3 +1150,169 @@ If you encounter issues:
**Congratulations!** You've successfully deployed PunimTag to cPanel! 🎉
---
## Appendix: Permanent Fix for Path Redirect Issues (Avoid Per-Feature `.htaccess` Rules)
This appendix documents the long-term fix for deployments where PunimTag runs under
path prefixes such as:
- `/punim-admin/`
- `/punim-viewer/`
- `/punim-api/`
### Why this matters
If the viewer app is only partially path-aware, some actions may still navigate to
root URLs like `/`, `/search`, or `/api/auth/session`. On WordPress + cPanel hosting,
those root paths are handled by WordPress instead of PunimTag, causing redirects,
404/400/405 errors, and broken flows.
Temporary `.htaccess` redirect rules can patch specific cases, but they are not
scalable. New features can introduce new root paths that require additional rules.
### Goal (Target State)
Use one consistent strategy so the app works without adding route-by-route redirects:
1. Viewer app is fully aware it runs at `/punim-viewer`
2. Auth routes resolve consistently under the same strategy
3. Reverse proxy behavior matches app expectations
4. `.htaccess` contains only minimal compatibility rules (or none)
---
### Step A - Configure Viewer Base Path (Code-Level, One-Time)
Edit `viewer-frontend/next.config.ts` and ensure these settings are present:
```ts
const nextConfig: NextConfig = {
basePath: '/punim-viewer',
assetPrefix: '/punim-viewer',
// ...existing config
};
```
Why:
- `basePath` makes Next.js routes and app navigation prefix-aware
- `assetPrefix` ensures static assets load from `/punim-viewer/_next/...`
---
### Step B - Align Viewer Environment Variables
In `viewer-frontend/.env` (production), keep auth/app URLs internally consistent.
Use the same canonical public origin and prefix strategy.
Recommended baseline:
```bash
NEXT_PUBLIC_APP_URL=https://your-domain.com/punim-viewer
NEXTAUTH_URL=https://your-domain.com/punim-viewer
AUTH_URL=https://your-domain.com/punim-viewer
AUTH_TRUST_HOST=true
NEXT_PUBLIC_API_URL=/punim-api
BACKEND_BASE_URL=/punim-api
```
Important:
- If your proxy forwards auth requests to Next.js as `/api/auth/*` (without prefix),
adjust `AUTH_URL`/`NEXTAUTH_URL` to match that behavior.
- Do not mix multiple base URL strategies at the same time.
---
### Step C - Enforce a Single URL Construction Pattern in Viewer
In viewer frontend code, avoid hardcoding root-absolute app routes for internal
navigation (`'/'`, `'/search'`, `'/upload'`, `'/api/auth/...'`) unless you are sure
Next base path handling applies.
Use shared helpers/utilities for:
- app route construction
- API route construction
- auth endpoints
This prevents new features from accidentally introducing root paths that bypass
`/punim-viewer`.
---
### Step D - Proxy Contract with Hosting Provider
Confirm the reverse proxy contract and keep it stable across deploys:
- `/punim-admin/*` -> React admin service
- `/punim-viewer/*` -> Next viewer service
- `/punim-api/*` -> FastAPI service root
For API mapping to FastAPI root:
- Browser endpoints remain `/punim-api/api/v1/...` for versioned routes
- Root routes like `/docs`, `/health`, and `/openapi.json` are under `/punim-api/...`
For viewer mapping:
- Ensure proxy behavior (path preserved vs stripped) is explicitly documented
- Match viewer env/auth settings to that behavior
---
### Step E - Rebuild and Restart Services
After changing base path/env/proxy assumptions:
```bash
cd ~/punimtag/viewer-frontend
rm -rf .next
npm run build
pm2 restart punimtag-viewer
```
If admin env/settings changed, rebuild/restart admin too.
---
### Step F - Validation Checklist (Must Pass)
Run these checks in browser network tab and direct URL tests:
1. Viewer entry:
- `https://your-domain.com/punim-viewer/` loads correctly
2. Viewer navigation:
- Actions (filters, clear all, login flows) remain under `/punim-viewer/...`
3. Auth session:
- `/punim-viewer/api/auth/session` succeeds (no 400/405)
4. Viewer API:
- Requests resolve under `/punim-viewer/api/...` (or your chosen consistent path)
5. Backend docs:
- `https://your-domain.com/punim-api/docs` loads
- `https://your-domain.com/punim-api/openapi.json` loads
6. Backend API:
- Versioned endpoints reachable at `/punim-api/api/v1/...`
---
### Step G - Decommission Temporary `.htaccess` Workarounds
Once target state is validated:
1. Remove route-by-route workaround redirects one by one
2. Retest after each removal
3. Keep only minimal generic rules required by hosting constraints
This prevents maintenance debt where each new viewer feature needs a new rewrite rule.
---
### Recommended Deployment Policy
Before every production deployment:
1. Confirm `next.config.ts` still has `basePath` + `assetPrefix`
2. Confirm viewer `.env` values are consistent with proxy contract
3. Build viewer from clean `.next`
4. Perform the validation checklist above
5. Only use `.htaccess` rewrites as temporary emergency mitigation
Following this policy gives a stable, permanent setup and avoids per-feature redirect
maintenance.

View File

@ -21,6 +21,11 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { isValidEmail } from '@/lib/utils';
interface User {
@ -387,17 +392,26 @@ export function ManageUsersContent() {
>
<Edit2 className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setUserToDelete(user);
setDeleteConfirmOpen(true);
}}
className="text-red-600 hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>
{user.isActive !== false && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={() => {
setUserToDelete(user);
setDeleteConfirmOpen(true);
}}
className="text-red-600 hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Deactivate User</p>
</TooltipContent>
</Tooltip>
)}
</div>
</td>
</tr>