@djangocfg/layouts 1.4.23 → 1.4.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.4.23",
3
+ "version": "1.4.26",
4
4
  "description": "Pre-built dashboard layouts, authentication pages, and admin templates for Next.js applications with Tailwind CSS",
5
5
  "keywords": [
6
6
  "layouts",
@@ -85,9 +85,9 @@
85
85
  "check": "tsc --noEmit"
86
86
  },
87
87
  "peerDependencies": {
88
- "@djangocfg/api": "^1.4.23",
89
- "@djangocfg/og-image": "^1.4.23",
90
- "@djangocfg/ui": "^1.4.23",
88
+ "@djangocfg/api": "^1.4.26",
89
+ "@djangocfg/og-image": "^1.4.26",
90
+ "@djangocfg/ui": "^1.4.26",
91
91
  "@hookform/resolvers": "^5.2.0",
92
92
  "consola": "^3.4.2",
93
93
  "lucide-react": "^0.468.0",
@@ -109,7 +109,7 @@
109
109
  "vidstack": "0.6.15"
110
110
  },
111
111
  "devDependencies": {
112
- "@djangocfg/typescript-config": "^1.4.23",
112
+ "@djangocfg/typescript-config": "^1.4.26",
113
113
  "@types/node": "^24.7.2",
114
114
  "@types/react": "19.2.2",
115
115
  "@types/react-dom": "19.2.1",
@@ -1,5 +1,6 @@
1
1
  import { Fragment } from 'react';
2
2
  import Head from 'next/head';
3
+ import { useRouter } from 'next/router';
3
4
  import { generateOgImageUrl } from '@djangocfg/og-image/utils';
4
5
 
5
6
  import { PageConfig } from '../../../types/pageConfig';
@@ -13,9 +14,48 @@ interface SeoProps {
13
14
  logoVector?: string;
14
15
  };
15
16
  siteUrl?: string;
17
+ /** Override canonical URL (defaults to current page URL) */
18
+ canonicalUrl?: string;
16
19
  }
17
20
 
18
- export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
21
+ /**
22
+ * Check if URL is absolute (starts with http:// or https://)
23
+ */
24
+ function isAbsoluteUrl(url: string): boolean {
25
+ return /^https?:\/\//i.test(url);
26
+ }
27
+
28
+ /**
29
+ * Get absolute site URL with smart fallbacks
30
+ *
31
+ * Priority:
32
+ * 1. Provided siteUrl (if absolute)
33
+ * 2. NEXT_PUBLIC_SITE_URL env var
34
+ * 3. window.location.origin (client-side only)
35
+ */
36
+ function getAbsoluteSiteUrl(siteUrl?: string): string | null {
37
+ // 1. Check if provided siteUrl is already absolute
38
+ if (siteUrl && isAbsoluteUrl(siteUrl)) {
39
+ return siteUrl.replace(/\/$/, ''); // Remove trailing slash
40
+ }
41
+
42
+ // 2. Check NEXT_PUBLIC_SITE_URL env var
43
+ const envSiteUrl = process.env.NEXT_PUBLIC_SITE_URL;
44
+ if (envSiteUrl && isAbsoluteUrl(envSiteUrl)) {
45
+ return envSiteUrl.replace(/\/$/, '');
46
+ }
47
+
48
+ // 3. Client-side fallback (not available during SSR)
49
+ if (typeof window !== 'undefined') {
50
+ return window.location.origin;
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ export default function Seo({ pageConfig, icons, siteUrl, canonicalUrl }: SeoProps) {
57
+ const router = useRouter();
58
+
19
59
  const {
20
60
  title,
21
61
  description,
@@ -29,6 +69,14 @@ export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
29
69
  const ogTitle = ogImage?.title || title;
30
70
  const ogSubtitle = ogImage?.subtitle || description;
31
71
 
72
+ // Get absolute site URL with smart fallbacks
73
+ const absoluteSiteUrl = getAbsoluteSiteUrl(siteUrl);
74
+
75
+ // Build canonical URL: custom > siteUrl + current path
76
+ const currentPath = router.asPath.split('?')[0]; // Remove query params
77
+ const absoluteCanonicalUrl = canonicalUrl
78
+ || (absoluteSiteUrl ? `${absoluteSiteUrl}${currentPath}` : null);
79
+
32
80
  // Generate OG image URL using @djangocfg/og-image utilities
33
81
  const ogImageUrl = ogImage
34
82
  ? generateOgImageUrl('/api/og', {
@@ -38,35 +86,47 @@ export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
38
86
  })
39
87
  : null;
40
88
 
41
- // Make absolute URL if siteUrl provided
42
- const absoluteOgImageUrl = ogImageUrl && siteUrl ? `${siteUrl}${ogImageUrl}` : ogImageUrl;
89
+ // Make absolute URL - only render og:image if we have absolute site URL
90
+ const absoluteOgImageUrl = ogImageUrl && absoluteSiteUrl
91
+ ? `${absoluteSiteUrl}${ogImageUrl}`
92
+ : null;
43
93
 
44
94
  return (
45
95
  <Head>
46
96
  <title>{title}</title>
47
97
  <meta name="description" content={description} />
48
98
  {keywords && <meta name="keywords" content={keywords} />}
49
-
99
+
50
100
  {/* Favicon */}
51
101
  <link rel="icon" type="image/png" href={icons?.logo192 || '/favicon.png'} />
52
-
102
+
103
+ {/* Canonical URL - important for SEO */}
104
+ {absoluteCanonicalUrl && (
105
+ <link rel="canonical" href={absoluteCanonicalUrl} />
106
+ )}
107
+
53
108
  {/* Open Graph */}
54
109
  <meta property="og:title" content={openGraph?.title || ogTitle} />
55
110
  <meta property="og:description" content={openGraph?.description || ogSubtitle} />
56
111
  <meta property="og:type" content={openGraph?.type || 'website'} />
57
112
 
113
+ {/* OG Canonical URL */}
114
+ {absoluteCanonicalUrl && (
115
+ <meta property="og:url" content={absoluteCanonicalUrl} />
116
+ )}
117
+
58
118
  {/* Site Name */}
59
119
  {(openGraph?.siteName || pageConfig.projectName) && (
60
120
  <meta property="og:site_name" content={openGraph?.siteName || pageConfig.projectName} />
61
121
  )}
62
-
122
+
63
123
  {/* Twitter */}
64
124
  <meta name="twitter:card" content={twitter?.card || 'summary_large_image'} />
65
125
  <meta name="twitter:title" content={twitter?.title || ogTitle} />
66
126
  <meta name="twitter:description" content={twitter?.description || ogSubtitle} />
67
127
  {twitter?.site && <meta name="twitter:site" content={twitter.site} />}
68
128
  {twitter?.creator && <meta name="twitter:creator" content={twitter.creator} />}
69
-
129
+
70
130
  {/* OG Images */}
71
131
  {openGraph?.images?.length ? (
72
132
  openGraph.images.map((image, index) => (
@@ -94,7 +154,7 @@ export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
94
154
  ) : absoluteOgImageUrl ? (
95
155
  <meta name="twitter:image" content={absoluteOgImageUrl} />
96
156
  ) : null}
97
-
157
+
98
158
  {/* JSON-LD */}
99
159
  {jsonLd && (
100
160
  <script
@@ -106,4 +166,4 @@ export default function Seo({ pageConfig, icons, siteUrl }: SeoProps) {
106
166
  )}
107
167
  </Head>
108
168
  );
109
- }
169
+ }