Skip to content

Instantly share code, notes, and snippets.

@AceCodePt
Last active January 8, 2026 07:58
Show Gist options
  • Select an option

  • Save AceCodePt/7d03c6f2f282d6a9445e4e6e8ceb6e4a to your computer and use it in GitHub Desktop.

Select an option

Save AceCodePt/7d03c6f2f282d6a9445e4e6e8ceb6e4a to your computer and use it in GitHub Desktop.
Astro Generate Routes integration
import { defineConfig } from "astro/config";
import { routeGen } from "./generate-paths.mts";
// https://astro.build/config
export default defineConfig({
integrations: [routeGen()],
});
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