Skip to main content

Echo Trailing Slash

Introduction

When building web applications with Echo, one detail that's easy to overlook but important to get right is how your application handles trailing slashes in URLs. A trailing slash is the forward slash (/) that appears at the end of a URL path. For example, in https://example.com/users/, the slash after "users" is a trailing slash.

Handling trailing slashes consistently is important for:

  1. Search Engine Optimization (SEO) - Search engines may treat example.com/about/ and example.com/about as different URLs, potentially diluting your SEO efforts
  2. Avoiding Duplicate Content - Different URLs serving identical content is considered duplicate content
  3. Maintaining Consistent User Experience - Users should reach the same content regardless of whether they include a trailing slash

In this tutorial, we'll explore how Echo handles trailing slashes and how you can configure your application to manage them according to your needs.

Default Behavior in Echo

By default, Echo routes are exact-match, meaning that /users and /users/ are treated as different routes. If you define a handler for /users but not for /users/, accessing /users/ will return a 404 error.

Let's see this in action:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()

// This handler will only match exactly "/users"
e.GET("/users", func(c echo.Context) error {
return c.String(http.StatusOK, "Users page")
})

e.Logger.Fatal(e.Start(":8080"))
}

With this setup:

  • GET /users will return "Users page"
  • GET /users/ will return a 404 error

Echo's Trailing Slash Middleware

Echo provides middleware to handle trailing slashes automatically. The middleware can be configured to either:

  1. Add a trailing slash if it's missing
  2. Remove a trailing slash if it exists
  3. Redirect to the canonical URL with or without a trailing slash

Adding Trailing Slashes

If you want to ensure all your URLs have trailing slashes, you can use the AddTrailingSlash middleware:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Add trailing slash middleware
e.Use(middleware.AddTrailingSlash())

// This handler will match "/users/"
e.GET("/users/", func(c echo.Context) error {
return c.String(http.StatusOK, "Users page with trailing slash")
})

e.Logger.Fatal(e.Start(":8080"))
}

Now:

  • GET /users will be internally changed to /users/
  • Both URLs will work and serve the same content

Removing Trailing Slashes

Conversely, if you prefer URLs without trailing slashes, you can use the RemoveTrailingSlash middleware:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Remove trailing slash middleware
e.Use(middleware.RemoveTrailingSlash())

// This handler will match "/users"
e.GET("/users", func(c echo.Context) error {
return c.String(http.StatusOK, "Users page without trailing slash")
})

e.Logger.Fatal(e.Start(":8080"))
}

Now:

  • GET /users/ will be internally changed to /users
  • Both URLs will work and serve the same content

Redirecting Instead of Rewriting

The examples above use internal URL rewriting, which means the URL in the browser's address bar doesn't change. For SEO purposes, it's often better to redirect users to the canonical URL.

Redirecting to URLs with Trailing Slashes

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Add trailing slash middleware with redirect
e.Use(middleware.AddTrailingSlashWithConfig(middleware.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently, // 301 redirect
}))

e.GET("/users/", func(c echo.Context) error {
return c.String(http.StatusOK, "Users page")
})

e.Logger.Fatal(e.Start(":8080"))
}

With this configuration:

  • When a user accesses /users, they'll be redirected to /users/ with a 301 (Moved Permanently) status code
  • Search engines will understand that /users/ is the canonical URL

Redirecting to URLs without Trailing Slashes

Similarly, you can redirect to URLs without trailing slashes:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Remove trailing slash middleware with redirect
e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently, // 301 redirect
}))

e.GET("/users", func(c echo.Context) error {
return c.String(http.StatusOK, "Users page")
})

e.Logger.Fatal(e.Start(":8080"))
}

Real-World Example: Building a Blog with Consistent URLs

Let's see a practical example of using trailing slash handling in a blog application:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Let's standardize on URLs without trailing slashes
e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently,
}))

// Blog routes
e.GET("/blog", listArticles)
e.GET("/blog/article/:slug", getArticle)
e.GET("/blog/category/:name", listCategoryArticles)

// Admin routes
e.GET("/admin/dashboard", adminDashboard)
e.GET("/admin/articles/new", newArticleForm)
e.POST("/admin/articles", createArticle)

e.Logger.Fatal(e.Start(":8080"))
}

func listArticles(c echo.Context) error {
return c.Render(http.StatusOK, "article-list.html", map[string]interface{}{
"articles": fetchAllArticles(),
})
}

func getArticle(c echo.Context) error {
slug := c.Param("slug")
article, found := findArticleBySlug(slug)
if !found {
return c.String(http.StatusNotFound, "Article not found")
}
return c.Render(http.StatusOK, "article-detail.html", article)
}

// Other handler functions and helpers...
func listCategoryArticles(c echo.Context) error {
// Implementation omitted for brevity
return nil
}

func adminDashboard(c echo.Context) error {
// Implementation omitted for brevity
return nil
}

func newArticleForm(c echo.Context) error {
// Implementation omitted for brevity
return nil
}

func createArticle(c echo.Context) error {
// Implementation omitted for brevity
return nil
}

func fetchAllArticles() []interface{} {
// This would normally fetch from a database
return []interface{}{}
}

func findArticleBySlug(slug string) (interface{}, bool) {
// This would normally fetch from a database
return nil, false
}

In this blog application:

  1. All URLs without trailing slashes are treated as canonical
  2. If someone visits a URL with a trailing slash (e.g., /blog/), they're redirected to the version without it (/blog)
  3. This ensures consistent URLs throughout the site and avoids SEO penalties for duplicate content

Special Consideration: Static Files

When dealing with static files, it's important to note how trailing slash handling interacts with file serving in Echo:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Remove trailing slashes from all URLs
e.Use(middleware.RemoveTrailingSlash())

// Serve static files
e.Static("/static", "public")

e.Logger.Fatal(e.Start(":8080"))
}

In this case:

  • /static/style.css will serve the file public/style.css
  • /static/ would normally show the directory index, but with RemoveTrailingSlash(), the request becomes /static, which might not behave as expected

If you need directory browsing to work properly, you might need to adjust your trailing slash configuration or handle directory paths specially.

Summary

Handling trailing slashes consistently in your Echo application is important for SEO optimization and providing a smooth user experience. The key points to remember are:

  1. By default, Echo treats /path and /path/ as different routes
  2. Echo provides middleware to add or remove trailing slashes automatically
  3. For SEO, it's best to redirect to a canonical URL rather than rewriting internally
  4. Consistently use either trailing slashes or no trailing slashes throughout your application
  5. Consider how trailing slash handling affects static file serving

Exercises

  1. Create a simple Echo application that enforces URLs with trailing slashes and redirects users with 301 status codes.
  2. Modify the blog example to use trailing slashes instead of removing them.
  3. Build a small API that serves both HTML content and JSON data, keeping URL conventions consistent.
  4. Implement a custom middleware that handles trailing slashes differently based on the path (e.g., remove for API routes, add for HTML pages).

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)