@agility/create-next-app 1.0.0-beta.2 → 1.0.0-beta.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"copyTemplates.d.ts","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C3F"}
1
+ {"version":3,"file":"copyTemplates.d.ts","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAS3C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD3F"}
@@ -8,6 +8,10 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const ora_1 = __importDefault(require("ora"));
10
10
  const copyDirectory_1 = require("./copyDirectory");
11
+ // Folders that should be copied to project root instead of src/
12
+ const ROOT_FOLDERS = ['.claude', 'node', 'data'];
13
+ // Files that should be copied to project root instead of src/
14
+ const ROOT_FILES = [];
11
15
  /**
12
16
  * Copies template files to the project
13
17
  * @param projectPath - Path to the project
@@ -22,17 +26,22 @@ async function copyTemplates(projectPath, options) {
22
26
  if (!fs_1.default.existsSync(srcDir)) {
23
27
  fs_1.default.mkdirSync(srcDir, { recursive: true });
24
28
  }
25
- // Copy all template files to src directory, except .claude
29
+ // Copy all template files to src directory, except special folders/files
26
30
  const entries = fs_1.default.readdirSync(templatesDir, { withFileTypes: true });
27
31
  for (const entry of entries) {
28
32
  const srcPath = path_1.default.join(templatesDir, entry.name);
29
- // .claude folder should be copied to project root, not src
30
- if (entry.name === '.claude') {
31
- const destPath = path_1.default.join(projectPath, '.claude');
32
- if (!fs_1.default.existsSync(destPath)) {
33
- fs_1.default.mkdirSync(destPath, { recursive: true });
33
+ // Check if this should go to project root
34
+ if (ROOT_FOLDERS.includes(entry.name) || ROOT_FILES.includes(entry.name)) {
35
+ const destPath = path_1.default.join(projectPath, entry.name);
36
+ if (entry.isDirectory()) {
37
+ if (!fs_1.default.existsSync(destPath)) {
38
+ fs_1.default.mkdirSync(destPath, { recursive: true });
39
+ }
40
+ await (0, copyDirectory_1.copyDirectory)(srcPath, destPath);
41
+ }
42
+ else {
43
+ fs_1.default.copyFileSync(srcPath, destPath);
34
44
  }
35
- await (0, copyDirectory_1.copyDirectory)(srcPath, destPath);
36
45
  }
37
46
  else {
38
47
  // All other files go to src directory
@@ -48,6 +57,8 @@ async function copyTemplates(projectPath, options) {
48
57
  }
49
58
  }
50
59
  }
60
+ // Add prebuild script to package.json
61
+ await addPrebuildScript(projectPath);
51
62
  spinner.succeed('Template files copied');
52
63
  }
53
64
  catch (error) {
@@ -55,4 +66,21 @@ async function copyTemplates(projectPath, options) {
55
66
  throw error;
56
67
  }
57
68
  }
69
+ /**
70
+ * Adds the prebuild script to package.json
71
+ */
72
+ async function addPrebuildScript(projectPath) {
73
+ const packageJsonPath = path_1.default.join(projectPath, 'package.json');
74
+ if (fs_1.default.existsSync(packageJsonPath)) {
75
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
76
+ // Add prebuild script
77
+ packageJson.scripts = packageJson.scripts || {};
78
+ packageJson.scripts.prebuild = 'npx tsx node/prebuild.ts';
79
+ // Update build script to run prebuild first
80
+ if (packageJson.scripts.build && !packageJson.scripts.build.includes('prebuild')) {
81
+ packageJson.scripts.build = 'npm run prebuild && next build';
82
+ }
83
+ fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
84
+ }
85
+ }
58
86
  //# sourceMappingURL=copyTemplates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copyTemplates.js","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":";;;;;AAWA,sCA4CC;AAvDD,4CAAoB;AACpB,gDAAwB;AACxB,8CAAsB;AAEtB,mDAAgD;AAEhD;;;;GAIG;AACI,KAAK,UAAU,aAAa,CAAC,WAAmB,EAAE,OAAmB;IAC1E,MAAM,OAAO,GAAG,IAAA,aAAG,EAAC,sCAAsC,CAAC,CAAC,KAAK,EAAE,CAAC;IAEpE,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAE7C,8BAA8B;QAC9B,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,YAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,2DAA2D;QAC3D,MAAM,OAAO,GAAG,YAAE,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAEpD,2DAA2D;YAC3D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;gBACnD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9C,CAAC;gBACD,MAAM,IAAA,6BAAa,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,sCAAsC;gBACtC,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAC7B,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC9C,CAAC;oBACD,MAAM,IAAA,6BAAa,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,YAAE,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC9C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"copyTemplates.js","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":";;;;;AAiBA,sCAmDC;AApED,4CAAoB;AACpB,gDAAwB;AACxB,8CAAsB;AAEtB,mDAAgD;AAEhD,gEAAgE;AAChE,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD,8DAA8D;AAC9D,MAAM,UAAU,GAAa,EAAE,CAAC;AAEhC;;;;GAIG;AACI,KAAK,UAAU,aAAa,CAAC,WAAmB,EAAE,OAAmB;IAC1E,MAAM,OAAO,GAAG,IAAA,aAAG,EAAC,sCAAsC,CAAC,CAAC,KAAK,EAAE,CAAC;IAEpE,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAE7C,8BAA8B;QAC9B,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,YAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,yEAAyE;QACzE,MAAM,OAAO,GAAG,YAAE,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAEpD,0CAA0C;YAC1C,IAAI,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzE,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACpD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAC7B,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC9C,CAAC;oBACD,MAAM,IAAA,6BAAa,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,YAAE,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,sCAAsC;gBACtC,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAC7B,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC9C,CAAC;oBACD,MAAM,IAAA,6BAAa,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,YAAE,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAErC,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC9C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IAClD,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAE/D,IAAI,YAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QAE1E,sBAAsB;QACtB,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,IAAI,EAAE,CAAC;QAChD,WAAW,CAAC,OAAO,CAAC,QAAQ,GAAG,0BAA0B,CAAC;QAE1D,4CAA4C;QAC5C,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjF,WAAW,CAAC,OAAO,CAAC,KAAK,GAAG,gCAAgC,CAAC;QAC/D,CAAC;QAED,YAAE,CAAC,aAAa,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjF,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agility/create-next-app",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Create a new Next.js project with Agility CMS integration",
5
5
  "main": "dist/cli/index.js",
6
6
  "bin": {
@@ -42,4 +42,4 @@
42
42
  "engines": {
43
43
  "node": ">=18.0.0"
44
44
  }
45
- }
45
+ }
@@ -4,6 +4,12 @@ import ora from 'ora';
4
4
  import type { CliOptions } from '../types';
5
5
  import { copyDirectory } from './copyDirectory';
6
6
 
7
+ // Folders that should be copied to project root instead of src/
8
+ const ROOT_FOLDERS = ['.claude', 'node', 'data'];
9
+
10
+ // Files that should be copied to project root instead of src/
11
+ const ROOT_FILES: string[] = [];
12
+
7
13
  /**
8
14
  * Copies template files to the project
9
15
  * @param projectPath - Path to the project
@@ -21,19 +27,23 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
21
27
  fs.mkdirSync(srcDir, { recursive: true });
22
28
  }
23
29
 
24
- // Copy all template files to src directory, except .claude
30
+ // Copy all template files to src directory, except special folders/files
25
31
  const entries = fs.readdirSync(templatesDir, { withFileTypes: true });
26
32
 
27
33
  for (const entry of entries) {
28
34
  const srcPath = path.join(templatesDir, entry.name);
29
35
 
30
- // .claude folder should be copied to project root, not src
31
- if (entry.name === '.claude') {
32
- const destPath = path.join(projectPath, '.claude');
33
- if (!fs.existsSync(destPath)) {
34
- fs.mkdirSync(destPath, { recursive: true });
36
+ // Check if this should go to project root
37
+ if (ROOT_FOLDERS.includes(entry.name) || ROOT_FILES.includes(entry.name)) {
38
+ const destPath = path.join(projectPath, entry.name);
39
+ if (entry.isDirectory()) {
40
+ if (!fs.existsSync(destPath)) {
41
+ fs.mkdirSync(destPath, { recursive: true });
42
+ }
43
+ await copyDirectory(srcPath, destPath);
44
+ } else {
45
+ fs.copyFileSync(srcPath, destPath);
35
46
  }
36
- await copyDirectory(srcPath, destPath);
37
47
  } else {
38
48
  // All other files go to src directory
39
49
  const destPath = path.join(srcDir, entry.name);
@@ -48,6 +58,9 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
48
58
  }
49
59
  }
50
60
 
61
+ // Add prebuild script to package.json
62
+ await addPrebuildScript(projectPath);
63
+
51
64
  spinner.succeed('Template files copied');
52
65
  } catch (error) {
53
66
  spinner.fail('Failed to copy template files');
@@ -55,3 +68,25 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
55
68
  }
56
69
  }
57
70
 
71
+ /**
72
+ * Adds the prebuild script to package.json
73
+ */
74
+ async function addPrebuildScript(projectPath: string): Promise<void> {
75
+ const packageJsonPath = path.join(projectPath, 'package.json');
76
+
77
+ if (fs.existsSync(packageJsonPath)) {
78
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
79
+
80
+ // Add prebuild script
81
+ packageJson.scripts = packageJson.scripts || {};
82
+ packageJson.scripts.prebuild = 'npx tsx node/prebuild.ts';
83
+
84
+ // Update build script to run prebuild first
85
+ if (packageJson.scripts.build && !packageJson.scripts.build.includes('prebuild')) {
86
+ packageJson.scripts.build = 'npm run prebuild && next build';
87
+ }
88
+
89
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
90
+ }
91
+ }
92
+
@@ -141,6 +141,9 @@ AGILITY_API_FETCH_KEY=your-fetch-api-key
141
141
  AGILITY_API_PREVIEW_KEY=your-preview-api-key
142
142
  AGILITY_LOCALES=en-us
143
143
  AGILITY_SITEMAP=website
144
+
145
+ # Optional: Build Hook URL for redirect rebuilds (see Deployment section)
146
+ BUILD_HOOK_URL=https://your-build-hook-url
144
147
  ```
145
148
 
146
149
  ## 🎯 Common Tasks
@@ -180,7 +183,8 @@ const posts = await getContentList({
180
183
 
181
184
  ```bash
182
185
  npm run dev # Start development server
183
- npm run build # Build for production
186
+ npm run prebuild # Rebuild redirect cache from Agility CMS
187
+ npm run build # Build for production (runs prebuild automatically)
184
188
  npm run start # Start production server
185
189
  npm run lint # Run ESLint
186
190
  ```
@@ -193,6 +197,7 @@ npm run lint # Run ESLint
193
197
  - ✅ **TypeScript** - Full type safety
194
198
  - ✅ **Caching** - ISR with on-demand revalidation
195
199
  - ✅ **Preview Mode** - See draft content before publishing
200
+ - ✅ **URL Redirects** - Managed in Agility CMS, applied via middleware
196
201
  - ✅ **AI-Friendly Docs** - Optimized for all AI coding tools
197
202
 
198
203
  ## 🔗 Key Concepts
@@ -209,6 +214,16 @@ Collections like blog posts, testimonials, team members. Fetched with `getConten
209
214
  ### Locales
210
215
  Multi-language support. Default locale (en-us) has clean URLs, others are prefixed (/fr/, /es/).
211
216
 
217
+ ### URL Redirects
218
+ URL redirects are managed in Agility CMS (Settings > URL Redirections) and applied via Next.js middleware.
219
+
220
+ **How it works:**
221
+ 1. The `prebuild` script fetches all redirects from Agility CMS and saves them to `data/redirections.json`
222
+ 2. The middleware checks incoming requests against this cache
223
+ 3. When a redirect is published in Agility CMS, the revalidate webhook triggers a rebuild (if `BUILD_HOOK_URL` is configured)
224
+
225
+ **Important:** Redirects are cached at build time. Changes to redirects in Agility CMS require a rebuild to take effect. Configure a build hook to automate this.
226
+
212
227
  ## 📖 Learn More
213
228
 
214
229
  ### Documentation
@@ -245,9 +260,28 @@ Set these in your hosting platform:
245
260
  - `AGILITY_GUID`
246
261
  - `AGILITY_API_FETCH_KEY`
247
262
  - `AGILITY_API_PREVIEW_KEY`
248
- - `AGILITY_SECURITY_KEY` (for webhooks)
263
+ - `AGILITY_SECURITY_KEY` (required for preview mode and Web Studio)
249
264
  - `AGILITY_LOCALES`
250
265
  - `AGILITY_SITEMAP`
266
+ - `BUILD_HOOK_URL` (optional, for automatic redirect rebuilds)
267
+
268
+ > **Important:** To enable preview mode and Web Studio integration from Agility CMS, you must add the **Security Key** from your Agility CMS instance to the `AGILITY_SECURITY_KEY` environment variable on your deployment platform. You can find this key in Agility CMS under **Settings > API Keys > Security Key**.
269
+
270
+ ### Configure Build Hook for URL Redirects
271
+
272
+ When URL redirects are changed in Agility CMS, the site needs to rebuild to pick up the changes. To automate this:
273
+
274
+ **Vercel:**
275
+ 1. Go to your project Settings > Git > Deploy Hooks
276
+ 2. Create a new hook (e.g., "Agility Redirect Rebuild")
277
+ 3. Copy the hook URL and add it to your environment variables as `BUILD_HOOK_URL`
278
+
279
+ **Netlify:**
280
+ 1. Go to Site Settings > Build & deploy > Build hooks
281
+ 2. Add a new build hook
282
+ 3. Copy the hook URL and add it to your environment variables as `BUILD_HOOK_URL`
283
+
284
+ When a redirect is published in Agility CMS (not a page or content item), the `/api/revalidate` webhook will automatically trigger this build hook.
251
285
 
252
286
  ## 🐛 Troubleshooting
253
287
 
@@ -0,0 +1,8 @@
1
+ # Agility CMS Robots Configuration
2
+ # This allows indexing of all pages and references the sitemap
3
+
4
+ User-agent: *
5
+ Allow: /
6
+
7
+ # Sitemap location (update the domain for your production site)
8
+ Sitemap: ${SITE_URL}/sitemap.xml
@@ -0,0 +1,56 @@
1
+ import { getSitemapFlat } from "@/lib/cms/getSitemapFlat"
2
+ import { locales, defaultLocale } from "@/lib/i18n/config"
3
+ import type { MetadataRoute } from "next"
4
+
5
+ /**
6
+ * Agility CMS Sitemap
7
+ *
8
+ * Generates a sitemap from Agility CMS pages using getSitemapFlat().
9
+ * Iterates through all configured locales and generates entries for each visible page.
10
+ */
11
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
12
+ const allSitemapEntries: MetadataRoute.Sitemap = []
13
+ const baseUrl = process.env.SITE_URL || "https://example.com"
14
+
15
+ // Generate sitemap entries for each locale
16
+ for (const locale of locales) {
17
+ const sitemapData = await getSitemapFlat({
18
+ channelName: process.env.AGILITY_SITEMAP || "website",
19
+ languageCode: locale
20
+ })
21
+
22
+ if (!sitemapData) continue
23
+
24
+ const localeEntries = Object.keys(sitemapData)
25
+ .filter((path) => {
26
+ const node = sitemapData[path]
27
+
28
+ // Skip folders and redirects
29
+ if (node.isFolder || node.redirect) {
30
+ return false
31
+ }
32
+
33
+ // Skip pages not visible in sitemap
34
+ if (!node.visible?.sitemap) {
35
+ return false
36
+ }
37
+
38
+ return true
39
+ })
40
+ .map((path, index) => {
41
+ // For default locale, don't add locale prefix to URL
42
+ const localizedPath = locale === defaultLocale ? path : `/${locale}${path}`
43
+
44
+ return {
45
+ url: index === 0 && path === "/" ? baseUrl : `${baseUrl}${localizedPath}`,
46
+ lastModified: new Date(),
47
+ changeFrequency: "daily" as const,
48
+ priority: path === "/" ? 1.0 : 0.8
49
+ }
50
+ })
51
+
52
+ allSitemapEntries.push(...localeEntries)
53
+ }
54
+
55
+ return allSitemapEntries
56
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "lastAccessDate": "",
3
+ "isUpToDate": false,
4
+ "items": {}
5
+ }
@@ -0,0 +1,35 @@
1
+ import type { Redirection, RedirectionsMap } from "../cms/getRedirections"
2
+
3
+ // Import redirections from the prebuild-generated JSON file
4
+ // This file is generated by running `npm run prebuild` before building
5
+ let allRedirects: RedirectionsMap = { lastAccessDate: "", isUpToDate: false, items: {} }
6
+
7
+ try {
8
+ // Dynamic import at build time - the file may not exist during development
9
+ allRedirects = require("../../../data/redirections.json") as RedirectionsMap
10
+ } catch {
11
+ // File doesn't exist yet - that's OK during development or first build
12
+ }
13
+
14
+ /**
15
+ * Check if a path should be redirected.
16
+ * Uses the prebuild-generated redirections.json file for fast lookups.
17
+ *
18
+ * Inspired by: https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced
19
+ *
20
+ * @param path - The URL path to check for redirects
21
+ * @returns The redirection if found, null otherwise
22
+ */
23
+ export const checkRedirect = async ({ path }: { path: string }): Promise<Redirection | null> => {
24
+ // Don't redirect the root path
25
+ if (path === "/") return null
26
+
27
+ // Get the redirections from the cached file
28
+ const redirections = allRedirects
29
+ if (!redirections || !redirections.items) return null
30
+
31
+ // Look up the path (case-insensitive)
32
+ const redirection = redirections.items[path.toLowerCase()]
33
+
34
+ return redirection || null
35
+ }
@@ -0,0 +1,29 @@
1
+ import fs from 'fs/promises'
2
+ import { getRedirections } from '../cms/getRedirections'
3
+
4
+ /**
5
+ * Rebuild the redirection cache.
6
+ * This should be called during prebuild (before `next build`) to ensure
7
+ * redirects are available at runtime in the middleware.
8
+ *
9
+ * Inspired by: https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced
10
+ */
11
+ export const rebuildRedirectCache = async () => {
12
+ console.log("Agility CMS => Rebuilding redirect cache...")
13
+ try {
14
+ // Force a rebuild of the redirection cache from Agility CMS
15
+ const redirections = await getRedirections({ forceUpdate: true })
16
+
17
+ const allKeys = Object.keys(redirections.items)
18
+ console.log(`Agility CMS => Found ${allKeys.length} redirects`)
19
+
20
+ // Save the redirections to the data folder for use in middleware
21
+ const fileName = 'data/redirections.json'
22
+ await fs.writeFile(fileName, JSON.stringify(redirections, null, 2), 'utf8')
23
+
24
+ console.log("Agility CMS => Redirect cache rebuilt successfully")
25
+
26
+ } catch (error) {
27
+ console.error("Error rebuilding redirect cache", error)
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ import { rebuildRedirectCache } from "../src/lib/cms-content/rebuildRedirectCache"
2
+
3
+ require("dotenv").config({
4
+ path: `.env.local`,
5
+ })
6
+
7
+ const doWork = async () => {
8
+ console.log("Agility CMS => Prebuild Started")
9
+
10
+ // Rebuild the redirects cache from Agility CMS
11
+ await rebuildRedirectCache()
12
+
13
+ console.log("Agility CMS => Prebuild Complete")
14
+ }
15
+
16
+ doWork()
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import type { NextRequest } from 'next/server'
3
3
  import { defaultLocale, locales, isValidLocale, getLocaleFromPathname, removeLocaleFromPathname } from './lib/i18n/config'
4
+ import { checkRedirect } from './lib/cms-content/checkRedirect'
4
5
 
5
6
  export async function proxy(request: NextRequest) {
6
7
  /*****************************
@@ -9,6 +10,7 @@ export async function proxy(request: NextRequest) {
9
10
  * 2: Check if we are exiting preview
10
11
  * 3: Check if this is a direct to a dynamic page
11
12
  * based on a content id
13
+ * 4: Check for URL redirects from Agility CMS
12
14
  *******************************/
13
15
 
14
16
  let pathname = request.nextUrl.pathname
@@ -38,6 +40,36 @@ export async function proxy(request: NextRequest) {
38
40
  return NextResponse.rewrite(dynredirectUrl)
39
41
  }
40
42
  } else if ((!ext || ext.length === 0)) {
43
+
44
+ /**********************
45
+ * CHECK FOR REDIRECT *
46
+ **********************/
47
+ const redirection = await checkRedirect({ path: request.nextUrl.pathname })
48
+
49
+ if (redirection) {
50
+ // Redirect to the destination URL
51
+ // Cache the redirect for 10 minutes (relative) or 1 hour (absolute)
52
+ if (redirection.destinationUrl.startsWith("/")) {
53
+ // Handle relative paths
54
+ const url = request.nextUrl.clone()
55
+ url.pathname = redirection.destinationUrl
56
+ return NextResponse.redirect(url, {
57
+ status: redirection.statusCode,
58
+ headers: {
59
+ "Cache-Control": "public, max-age=600, stale-while-revalidate"
60
+ }
61
+ })
62
+ } else {
63
+ // Handle absolute paths (external URLs)
64
+ return NextResponse.redirect(redirection.destinationUrl, {
65
+ status: redirection.statusCode,
66
+ headers: {
67
+ "Cache-Control": "public, max-age=3600, stale-while-revalidate"
68
+ }
69
+ })
70
+ }
71
+ }
72
+
41
73
  /**************************************
42
74
  * SPECIAL CASE FOR lang= QUERY PARAM *
43
75
  **************************************/