diff --git a/docs/guide/server-routing.md b/docs/guide/server-routing.md
index 16a3272e3..474682b4f 100644
--- a/docs/guide/server-routing.md
+++ b/docs/guide/server-routing.md
@@ -12,7 +12,7 @@ Create route handlers in `routes/**/*.ts`. Each file maps to a URL path based on
-Dynamic segments use brackets: `[id]` becomes a route parameter and `[...slug]` becomes a catch-all. Files or directories starting with `_` are ignored. Directories wrapped in parentheses like `(admin)` are route groups. They help organize files without changing the URL.
+Dynamic segments use brackets: `[id]` becomes a route parameter and `[...slug]` becomes a catch-all. Files or directories starting with `_` are ignored, and so are those starting with `.`, except `.well-known`. Directories wrapped in parentheses like `(admin)` are route groups. They help organize files without changing the URL.
### Environment-only routes
diff --git a/packages/void/src/shared/convention-files.ts b/packages/void/src/shared/convention-files.ts
index a31091856..843f826c8 100644
--- a/packages/void/src/shared/convention-files.ts
+++ b/packages/void/src/shared/convention-files.ts
@@ -2,7 +2,7 @@ import { globSync } from 'tinyglobby';
/** Handler files under `dir`, relative to it, skipping paths with a `_`-prefixed segment. */
export function listConventionFiles(dir: string): Array {
- return globSync('**/*.{ts,mts,js,mjs}', { cwd: dir }).filter(
- (filePath) => !filePath.split('/').some((segment) => segment.startsWith('_')),
- );
+ return globSync(['**/*.{ts,mts,js,mjs}', '.well-known/**/*.{ts,mts,js,mjs}'], {
+ cwd: dir,
+ }).filter((filePath) => !filePath.split('/').some((segment) => segment.startsWith('_')));
}
diff --git a/packages/void/test/unit/scan.test.ts b/packages/void/test/unit/scan.test.ts
index 4c5f861a2..0e8bc489e 100644
--- a/packages/void/test/unit/scan.test.ts
+++ b/packages/void/test/unit/scan.test.ts
@@ -73,6 +73,14 @@ describe('scanRoutes', () => {
expect(result.routes[0].pattern).toBe('/api/hello');
});
+ it('discovers routes under .well-known and ignores other dot paths', async () => {
+ createFile('routes/.well-known/security.txt.ts');
+ createFile('routes/.cache/hello.ts');
+
+ const result = await scanRoutes(TMP);
+ expect(result.routes.map((route) => route.pattern)).toEqual(['/.well-known/security.txt']);
+ });
+
it('discovers and sorts middleware by numeric prefix', async () => {
createFile('middleware/02.auth.ts');
createFile('middleware/01.logger.ts');