@@ -33,6 +33,7 @@ import {
3333 RenderOptions ,
3434 Route ,
3535 RouteModule ,
36+ RouterOptions ,
3637 UnknownPage ,
3738 UnknownPageModule ,
3839} from "./types.ts" ;
@@ -86,6 +87,7 @@ export class ServerContext {
8687 #error: ErrorPage ;
8788 #plugins: Plugin [ ] ;
8889 #builder: Builder | Promise < BuildSnapshot > | BuildSnapshot ;
90+ #routerOptions: RouterOptions ;
8991
9092 constructor (
9193 routes : Route [ ] ,
@@ -100,6 +102,7 @@ export class ServerContext {
100102 configPath : string ,
101103 jsxConfig : JSXConfig ,
102104 dev : boolean = isDevMode ( ) ,
105+ routerOptions : RouterOptions ,
103106 ) {
104107 this . #routes = routes ;
105108 this . #islands = islands ;
@@ -118,6 +121,7 @@ export class ServerContext {
118121 dev : this . #dev,
119122 jsxConfig,
120123 } ) ;
124+ this . #routerOptions = routerOptions ;
121125 }
122126
123127 /**
@@ -345,6 +349,7 @@ export class ServerContext {
345349 configPath ,
346350 jsxConfig ,
347351 dev ,
352+ opts . router ?? DEFAULT_ROUTER_OPTIONS ,
348353 ) ;
349354 }
350355
@@ -359,19 +364,25 @@ export class ServerContext {
359364 this . #middlewares,
360365 handlers . errorHandler ,
361366 ) ;
367+ const trailingSlashEnabled = this . #routerOptions?. trailingSlash ;
362368 return async function handler ( req : Request , connInfo : ConnInfo ) {
363369 // Redirect requests that end with a trailing slash to their non-trailing
364370 // slash counterpart.
365371 // Ex: /about/ -> /about
366372 const url = new URL ( req . url ) ;
367- if ( url . pathname . length > 1 && url . pathname . endsWith ( "/" ) ) {
373+ if (
374+ url . pathname . length > 1 && url . pathname . endsWith ( "/" ) &&
375+ ! trailingSlashEnabled
376+ ) {
368377 // Remove trailing slashes
369378 const path = url . pathname . replace ( / \/ + $ / , "" ) ;
370379 const location = `${ path } ${ url . search } ` ;
371380 return new Response ( null , {
372381 status : Status . TemporaryRedirect ,
373382 headers : { location } ,
374383 } ) ;
384+ } else if ( trailingSlashEnabled && ! url . pathname . endsWith ( "/" ) ) {
385+ return Response . redirect ( url . href + "/" , Status . PermanentRedirect ) ;
375386 }
376387
377388 return await withMiddlewares ( req , connInfo , inner ) ;
@@ -606,6 +617,9 @@ export class ServerContext {
606617 const createUnknownRender = genRender ( this . #notFound, Status . NotFound ) ;
607618
608619 for ( const route of this . #routes) {
620+ if ( this . #routerOptions. trailingSlash && route . pattern != "/" ) {
621+ route . pattern += "/" ;
622+ }
609623 const createRender = genRender ( route , Status . OK ) ;
610624 if ( typeof route . handler === "function" ) {
611625 routes [ route . pattern ] = {
@@ -778,6 +792,10 @@ const DEFAULT_RENDER_FN: RenderFunction = (_ctx, render) => {
778792 render ( ) ;
779793} ;
780794
795+ const DEFAULT_ROUTER_OPTIONS : RouterOptions = {
796+ trailingSlash : false ,
797+ } ;
798+
781799const DEFAULT_APP : AppModule = {
782800 default : ( { Component } ) => h ( Component , { } ) ,
783801} ;
0 commit comments