Last active
January 8, 2026 07:58
-
-
Save AceCodePt/7d03c6f2f282d6a9445e4e6e8ceb6e4a to your computer and use it in GitHub Desktop.
Astro Generate Routes integration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { defineConfig } from "astro/config"; | |
| import { routeGen } from "./generate-paths.mts"; | |
| // https://astro.build/config | |
| export default defineConfig({ | |
| integrations: [routeGen()], | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { mkdirSync, readdirSync, writeFileSync } from 'node:fs'; | |
| import { dirname, resolve, sep } from 'node:path'; | |
| import type { AstroIntegration } from 'astro'; | |
| interface RouteDefinition { | |
| varName: string; | |
| constDef: string; | |
| zodDef: string; | |
| } | |
| export const routeGen = ( | |
| prefix: string = 'PATH', | |
| generatedFilePath: string = './src/routes.gen.ts', | |
| ): AstroIntegration => { | |
| const generate = () => { | |
| const pagesDir = resolve('./src/pages'); | |
| const projectRoot = process.cwd(); | |
| const files = readdirSync(pagesDir, { recursive: true }) | |
| .map((f) => f.toString()) | |
| .filter((f) => { | |
| const parts = f.split(sep); | |
| const isPrivate = parts.some((part) => part.startsWith('_')); | |
| const isPage = | |
| f.endsWith('.astro') || f.endsWith('.ts') || f.endsWith('.md'); | |
| return !isPrivate && isPage; | |
| }); | |
| const routeDefs: RouteDefinition[] = files.map((file) => { | |
| // 1. Normalize Path | |
| let urlPath = | |
| '/' + | |
| file | |
| .replace(/\\/g, '/') | |
| .replace(/\.(astro|ts|md)$/, '') | |
| .replace(/\/index$/, ''); | |
| if (urlPath === '/index') urlPath = '/'; | |
| if (urlPath.length > 1 && urlPath.endsWith('/')) | |
| urlPath = urlPath.slice(0, -1); | |
| // 2. Generate Variable Name | |
| const rawVarName = urlPath | |
| .toUpperCase() | |
| .replace(/\/\[\.\.\./g, '___') | |
| .replace(/\/\[/g, '__') | |
| .replace(/[./]/g, '_') | |
| .replace(/[-\]]/g, ''); | |
| const varName = `${prefix}${rawVarName === '_' ? '_ROOT' : rawVarName}`; | |
| // 3. Handle Dynamic and Rest Parameters | |
| if (urlPath.includes('[')) { | |
| // Regex to find all [params] or [...rest] | |
| const segments = urlPath.match(/\[(?:\.\.\.)?[^\]]+\]/g) || []; | |
| const args: string[] = []; | |
| let templateStr = urlPath; | |
| segments.forEach((seg) => { | |
| const isRest = seg.includes('...'); | |
| const paramName = seg.replace(/[.[\]]/g, ''); | |
| if (isRest) { | |
| // TypeScript: string[] | number[] | |
| args.push(`${paramName}?: (string | number)[]`); | |
| // Template: Join array with slashes | |
| templateStr = templateStr.replace( | |
| `/${seg}`, | |
| `\${${paramName} && ${paramName}.length ? "/" + rest.join('/') : ""}`, | |
| ); | |
| } else { | |
| // TypeScript: string | number | |
| args.push(`${paramName}: string | number`); | |
| templateStr = templateStr.replace(seg, `\${${paramName}}`); | |
| } | |
| }); | |
| // Zod Definition | |
| // For rest params, we use z.string() to represent the remainder of the path | |
| const zodSegments = urlPath | |
| .split('/') | |
| .filter(Boolean) | |
| .map((p) => { | |
| if (p.startsWith('[...')) return `z.string()`; // Catch-all matches any string sequence | |
| if (p.startsWith('[')) return `z.union([z.string(), z.number()])`; | |
| return `"/ ${p}"`.replace(' ', ''); // Keep leading slash logic | |
| }); | |
| return { | |
| varName, | |
| constDef: `export const ${varName} = (${args.join(', ')}) => \`${templateStr}\` as const;`, | |
| zodDef: `z.templateLiteral(["/", ${zodSegments.join(', ')}])`, | |
| }; | |
| } | |
| // Static Routes | |
| return { | |
| varName, | |
| constDef: `export const ${varName} = "${urlPath}";`, | |
| zodDef: `z.literal(${varName})`, | |
| }; | |
| }); | |
| const template = `/* AUTO-GENERATED - DO NOT EDIT */ | |
| import { z } from "zod"; | |
| ${routeDefs.map((d) => d.constDef).join('\n')} | |
| export const routesSchema = z.union([ | |
| ${routeDefs.map((d) => d.zodDef).join(',\n ')} | |
| ]); | |
| export type Routes = z.infer<typeof routesSchema>; | |
| `; | |
| const fullOutputPath = resolve(projectRoot, generatedFilePath); | |
| mkdirSync(dirname(fullOutputPath), { recursive: true }); | |
| writeFileSync(fullOutputPath, template); | |
| }; | |
| return { | |
| name: 'route-gen', | |
| hooks: { | |
| 'astro:config:setup': ({ logger }) => { | |
| generate(); | |
| logger.info('Route types generated.'); | |
| }, | |
| 'astro:server:setup': ({ server }) => { | |
| server.watcher.on('all', (event, filePath) => { | |
| if ( | |
| filePath.includes('src/pages') && | |
| (event === 'add' || event === 'unlink') | |
| ) { | |
| generate(); | |
| } | |
| }); | |
| }, | |
| }, | |
| }; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment