pjdev2d-cli 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/package.json +1 -1
- package/templates/react-router/router/guards/private.route.tsx +39 -4
- package/templates/react-router/router/index.ts +51 -9
- package/templates/react-router/router/layouts/error.layout.tsx +10 -1
- package/templates/react-router/routes/page-one/index.tsx +83 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -5,17 +5,52 @@ export function PrivateRoute() {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
|
+
# NOTE : use this when you want to redirect user to login page if he is already logged out.
|
|
9
|
+
# Includes the premium "Redirect Back" pattern to preserve user navigation state.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { Navigate, Outlet } from "react-router-dom";
|
|
11
|
+
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
|
12
12
|
|
|
13
13
|
export default function PrivateRoute() {
|
|
14
14
|
const token = getLocalAuthToken();
|
|
15
|
+
const location = useLocation();
|
|
16
|
+
|
|
15
17
|
if (!token) {
|
|
16
|
-
|
|
18
|
+
// Passes the requested path inside search params so you can redirect back after successful login
|
|
19
|
+
return (
|
|
20
|
+
<Navigate
|
|
21
|
+
to={`/page_auth?redirectTo=${encodeURIComponent(location.pathname + location.search)}`}
|
|
22
|
+
replace
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
17
25
|
}
|
|
18
26
|
return <Outlet />;
|
|
19
27
|
}
|
|
20
28
|
|
|
29
|
+
# NOTE: HOW THE LOGIN COMPONENT CONSUMES THE "redirectTo" PARAMETER
|
|
30
|
+
|
|
31
|
+
### The Bookmarking Lifecycle Flow:
|
|
32
|
+
1. **Bookmarked Access:** A logged-out user tries to access a bookmarked private route directly:
|
|
33
|
+
`https://your-app.com/page_two`
|
|
34
|
+
2. **Redirect to Auth:** The `PrivateRoute` guard intercepts the request, captures the target pathname (`/page_two`), and redirects them to the login screen with the target appended:
|
|
35
|
+
`https://your-app.com/page_auth?redirectTo=%2Fpage_two`
|
|
36
|
+
3. **Login:** The user submits their login credentials.
|
|
37
|
+
4. **Target Restoration:** Upon successful authentication, the Login page checks the URL for `redirectTo` and navigates the user straight to `/page_two` instead of the generic homepage `/`.
|
|
38
|
+
|
|
39
|
+
### Code Implementation Example:
|
|
40
|
+
```typescript
|
|
41
|
+
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
42
|
+
|
|
43
|
+
export default function LoginPage() {
|
|
44
|
+
const navigate = useNavigate();
|
|
45
|
+
const [searchParams] = useSearchParams();
|
|
46
|
+
|
|
47
|
+
const handleLoginSuccess = () => {
|
|
48
|
+
// 1. Read the redirectTo value (e.g., "/page_two") or default to "/"
|
|
49
|
+
const destination = searchParams.get("redirectTo") || "/";
|
|
50
|
+
|
|
51
|
+
// 2. Redirect the user back to their bookmarked page instead of the root page!
|
|
52
|
+
navigate(destination, { replace: true });
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
```
|
|
21
56
|
*/
|
|
@@ -8,7 +8,6 @@ import { MainLayout } from "./layouts/main.layout";
|
|
|
8
8
|
|
|
9
9
|
const PublicRoutes: RouteObject[] = [
|
|
10
10
|
{
|
|
11
|
-
path: PATH.page_root(),
|
|
12
11
|
Component: PublicRoute,
|
|
13
12
|
children: [
|
|
14
13
|
{
|
|
@@ -24,13 +23,6 @@ const PublicRoutes: RouteObject[] = [
|
|
|
24
23
|
},
|
|
25
24
|
],
|
|
26
25
|
},
|
|
27
|
-
{
|
|
28
|
-
path: PATH.not_found(),
|
|
29
|
-
lazy: async () => {
|
|
30
|
-
const module = await import("../routes/not-found");
|
|
31
|
-
return { Component: module.default };
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
26
|
],
|
|
35
27
|
},
|
|
36
28
|
];
|
|
@@ -73,7 +65,21 @@ const PrivateRoutes: RouteObject[] = [
|
|
|
73
65
|
},
|
|
74
66
|
];
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
const NotFoundRoutes: RouteObject[] = [
|
|
69
|
+
{
|
|
70
|
+
path: PATH.not_found(), // Wildcard catch-all must be at the end of the root array
|
|
71
|
+
lazy: async () => {
|
|
72
|
+
const module = await import("../routes/not-found");
|
|
73
|
+
return { Component: module.default };
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export const router = createBrowserRouter([
|
|
79
|
+
...PublicRoutes,
|
|
80
|
+
...PrivateRoutes,
|
|
81
|
+
...NotFoundRoutes,
|
|
82
|
+
]);
|
|
77
83
|
|
|
78
84
|
/*
|
|
79
85
|
|
|
@@ -141,4 +147,40 @@ the old way is
|
|
|
141
147
|
],
|
|
142
148
|
},
|
|
143
149
|
|
|
150
|
+
---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
# NOTE : DYNAMIC PAGE TITLES (SEO BEST PRACTICE)
|
|
153
|
+
To set browser tab titles dynamically on page transitions, create a custom hook and call it inside your page route components:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { useEffect } from "react";
|
|
157
|
+
|
|
158
|
+
export function useDocumentTitle(title: string) {
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const original = document.title;
|
|
161
|
+
document.title = `${title} | My App`;
|
|
162
|
+
return () => {
|
|
163
|
+
document.title = original;
|
|
164
|
+
};
|
|
165
|
+
}, [title]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// In routes/page-one.tsx:
|
|
169
|
+
// useDocumentTitle("Analytics Page");
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
# NOTE : ROUTE-LEVEL SUSPENSE VS. COMPONENT-LEVEL SKELETONS
|
|
175
|
+
|
|
176
|
+
1. **Why we place `<Suspense>` at the very top (wrapping `<RouterProvider>`):**
|
|
177
|
+
- **Code Chunk Loading:** Because we lazy-load routes (`lazy: async () => ...`), React Router has to download the page's `.js` chunk file from the server when navigating.
|
|
178
|
+
- **Preventing UI Freezes:** If you click a link and do not have a top-level Suspense boundary, the app will freeze or flicker on the old page until the file download finishes.
|
|
179
|
+
- **Best Practice:** Wrap the router in `<Suspense fallback={<TopBarProgressBar />} />` so the user gets instant progress feedback when clicking links.
|
|
180
|
+
|
|
181
|
+
2. **Why we use Component-level skeletons:**
|
|
182
|
+
- **Data Loading:** Once the page code is downloaded and mounted, your API data fetching starts.
|
|
183
|
+
- **Interactive UI:** This is where you render custom skeleton loaders inside your page components while waiting for TanStack Query data, offering a premium and localized loading layout.
|
|
144
184
|
*/
|
|
185
|
+
|
|
186
|
+
|
|
@@ -2,5 +2,14 @@ import { Outlet, useRouteError } from "react-router-dom";
|
|
|
2
2
|
|
|
3
3
|
export function ErrorLayout() {
|
|
4
4
|
const error = useRouteError();
|
|
5
|
-
return
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<h1>Something went wrong!</h1>
|
|
8
|
+
<p>
|
|
9
|
+
{error instanceof Error
|
|
10
|
+
? error.message
|
|
11
|
+
: "An unexpected error occurred"}
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
6
15
|
}
|
|
@@ -1,3 +1,85 @@
|
|
|
1
1
|
export default function PageOne() {
|
|
2
2
|
return <div>Page One</div>;
|
|
3
|
-
}
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
# NOTE: FRAMEWORK-LEVEL META & PRERENDERING CONFIGURATION (SEO)
|
|
7
|
+
|
|
8
|
+
If you are using React Router v7 and want static HTML files compiled for SEO search crawlers, follow these steps:
|
|
9
|
+
|
|
10
|
+
-------------------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
1. ROUTE META EXPORT (Add this inside this page file):
|
|
13
|
+
```typescript
|
|
14
|
+
import type { MetaFunction } from "react-router-dom";
|
|
15
|
+
|
|
16
|
+
export const meta: MetaFunction = () => {
|
|
17
|
+
return [
|
|
18
|
+
{ title: "Page One Analytics | My App" },
|
|
19
|
+
{ name: "description", content: "Analyze product sales and user logs." },
|
|
20
|
+
];
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
-------------------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
2. PRERENDER SETTINGS (Add this in your react-router.config.ts / vite.config.ts):
|
|
27
|
+
```typescript
|
|
28
|
+
export default {
|
|
29
|
+
async prerender() {
|
|
30
|
+
return ["/", "/page_one", "/page_two"]; // Routes to generate as static HTML pages
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
-------------------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
3. HOW THE BUILD CHANGES (Build output comparison):
|
|
38
|
+
|
|
39
|
+
OLD BUILD (Standard Single Page Application):
|
|
40
|
+
- Outputs only a single empty "index.html" page.
|
|
41
|
+
- Crawlers see no initial page HTML or titles when visiting "/page_one" until JS loads.
|
|
42
|
+
|
|
43
|
+
NEW BUILD (Prerendered SPA):
|
|
44
|
+
- Outputs directory folders containing index.html files:
|
|
45
|
+
dist/
|
|
46
|
+
├── index.html <- for "/"
|
|
47
|
+
├── page_one/
|
|
48
|
+
│ └── index.html <- for "/page_one" (contains pre-rendered metadata)
|
|
49
|
+
└── page_two/
|
|
50
|
+
└── index.html <- for "/page_two" (contains pre-rendered metadata)
|
|
51
|
+
|
|
52
|
+
-------------------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
4. SERVER SETTINGS (VPS Deployment):
|
|
55
|
+
|
|
56
|
+
For a standard VPS server (like Nginx), configure it to serve static files from your build directory.
|
|
57
|
+
Nginx will look for the folder-based index files (e.g., /page_one/index.html) first.
|
|
58
|
+
|
|
59
|
+
NGINX VPS CONFIGURATION EXAMPLE (/etc/nginx/sites-available/default):
|
|
60
|
+
```nginx
|
|
61
|
+
server {
|
|
62
|
+
listen 80;
|
|
63
|
+
server_name your-app.com;
|
|
64
|
+
|
|
65
|
+
# Point to your build distribution directory on the VPS
|
|
66
|
+
root /var/www/your-app/dist;
|
|
67
|
+
|
|
68
|
+
index index.html;
|
|
69
|
+
|
|
70
|
+
location / {
|
|
71
|
+
# 1. Checks if exact file exists ($uri)
|
|
72
|
+
# 2. Checks if a directory index.html exists ($uri/) -> Serves pre-rendered SEO pages!
|
|
73
|
+
# 3. Falls back to root index.html if route is handled client-side
|
|
74
|
+
try_files $uri $uri/ /index.html;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Optional: Proxy API requests directly to your Laravel Backend
|
|
78
|
+
location /api/ {
|
|
79
|
+
proxy_pass http://127.0.0.1:8000; # Address where your Laravel backend runs
|
|
80
|
+
proxy_set_header Host $host;
|
|
81
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
*/
|