Repository: gobuffalo/buffalo
Pull request #2207 (2022-02-03 13:21:01)

Development


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index aad738c7..3cb687a3 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,8 +4,14 @@ updates:
     directory: "/"
     schedule:
       interval: "daily"
+    target-branch: "development"
+    labels:
+      - "gomod"
 
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
       interval: "daily"
+    target-branch: "development"
+    labels:
+      - "github-actions"
diff --git a/app.go b/app.go
index aec94b3a..804061e4 100644
--- a/app.go
+++ b/app.go
@@ -75,7 +75,6 @@ func New(opts Options) *App {
 	}
 	a.Use(a.PanicHandler)
 	a.Use(RequestLogger)
-	a.Use(sessionSaver)
 
 	return a
 }
diff --git a/default_context.go b/default_context.go
index e648948a..0aad9240 100644
--- a/default_context.go
+++ b/default_context.go
@@ -139,6 +139,9 @@ func (d *DefaultContext) Render(status int, rr render.Renderer) error {
 	if d.Session() != nil {
 		d.Flash().Clear()
 		d.Flash().persist(d.Session())
+		if err := d.Session().Save(); err != nil {
+			return HTTPError{Status: http.StatusInternalServerError, Cause: err}
+		}
 	}
 
 	d.Response().Header().Set("Content-Type", rr.ContentType())
@@ -191,7 +194,13 @@ var mapType = reflect.ValueOf(map[string]interface{}{}).Type()
 
 // Redirect a request with the given status to the given URL.
 func (d *DefaultContext) Redirect(status int, url string, args ...interface{}) error {
-	d.Flash().persist(d.Session())
+	if d.Session() != nil {
+		d.Flash().Clear()
+		d.Flash().persist(d.Session())
+		if err := d.Session().Save(); err != nil {
+			return HTTPError{Status: http.StatusInternalServerError, Cause: err}
+		}
+	}
 
 	if strings.HasSuffix(url, "Path()") {
 		if len(args) > 1 {
diff --git a/error.dev.html b/error.dev.html
new file mode 100644
index 00000000..8629916e
--- /dev/null
+++ b/error.dev.html
@@ -0,0 +1,647 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>
+        <%= status %> - ERROR!
+    </title>
+    <style>
+        html {
+            font-family: sans-serif;
+            -webkit-text-size-adjust: 100%;
+            -ms-text-size-adjust: 100%
+        }
+
+        body {
+            margin: 0
+        }
+
+        header {
+            display: block
+        }
+
+        a {
+            background-color: transparent
+        }
+
+        a:active,
+        a:hover {
+            outline: 0
+        }
+
+        h1 {
+            margin: .67em 0;
+            font-size: 2em
+        }
+
+        img {
+            border: 0
+        }
+
+        pre {
+            overflow: auto
+        }
+
+        code,
+        pre {
+            font-family: monospace, monospace;
+            font-size: 1em
+        }
+
+        table {
+            border-spacing: 0;
+            border-collapse: collapse
+        }
+
+        td,
+        th {
+            padding: 0
+        }
+
+        @media print {
+            * {
+                color: #000 !important;
+                text-shadow: none !important;
+                background: 0 0 !important;
+                -webkit-box-shadow: none !important;
+                box-shadow: none !important
+            }
+
+            a,
+            a:visited {
+                text-decoration: underline
+            }
+
+            a[href]:after {
+                content: " ("attr(href) ")"
+            }
+
+            pre {
+                border: 1px solid #999;
+                page-break-inside: avoid
+            }
+
+            thead {
+                display: table-header-group
+            }
+
+            img,
+            tr {
+                page-break-inside: avoid
+            }
+
+            img {
+                max-width: 100% !important
+            }
+
+            h3 {
+                orphans: 3;
+                widows: 3
+            }
+
+            h3 {
+                page-break-after: avoid
+            }
+
+            .table {
+                border-collapse: collapse !important
+            }
+
+            .table td,
+            .table th {
+                background-color: #fff !important
+            }
+        }
+
+        @font-face {
+            font-family: 'Glyphicons Halflings';
+            src: url(../fonts/glyphicons-halflings-regular.eot);
+            src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'), url(../fonts/glyphicons-halflings-regular.woff) format('woff'), url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')
+        }
+
+        * {
+            -webkit-box-sizing: border-box;
+            -moz-box-sizing: border-box;
+            box-sizing: border-box
+        }
+
+        html {
+            font-size: 10px;
+            -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
+        }
+
+        body {
+            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+            font-size: 14px;
+            line-height: 1.42857143;
+            color: #333;
+            background-color: #fff
+        }
+
+        a {
+            color: #337ab7;
+            text-decoration: none
+        }
+
+        a:focus,
+        a:hover {
+            color: #23527c;
+            text-decoration: underline
+        }
+
+        a:focus {
+            outline: 5px auto -webkit-focus-ring-color;
+            outline-offset: -2px
+        }
+
+        img {
+            vertical-align: middle
+        }
+
+        h1,
+        h3 {
+            font-family: inherit;
+            font-weight: 500;
+            line-height: 1.1;
+            color: inherit
+        }
+
+        h1,
+        h3 {
+            margin-top: 20px;
+            margin-bottom: 10px
+        }
+
+        h1 {
+            font-size: 36px
+        }
+
+        h3 {
+            font-size: 24px
+        }
+
+        code,
+        pre {
+            font-family: Menlo, Monaco, Consolas, "Courier New", monospace
+        }
+
+        code {
+            padding: 2px 4px;
+            font-size: 90%;
+            color: #c7254e;
+            background-color: #f9f2f4;
+            border-radius: 4px
+        }
+
+        pre {
+            display: block;
+            padding: 9.5px;
+            margin: 0 0 10px;
+            font-size: 13px;
+            line-height: 1.42857143;
+            color: #333;
+            word-break: break-all;
+            word-wrap: break-word;
+            background-color: #f5f5f5;
+            border: 1px solid #ccc;
+            border-radius: 4px
+        }
+
+        .container {
+            padding-right: 15px;
+            padding-left: 15px;
+            margin-right: auto;
+            margin-left: auto
+        }
+
+        @media (min-width:768px) {
+            .container {
+                width: 750px
+            }
+        }
+
+        @media (min-width:992px) {
+            .container {
+                width: 970px
+            }
+        }
+
+        @media (min-width:1200px) {
+            .container {
+                width: 1170px
+            }
+        }
+
+        .row {
+            margin-right: -15px;
+            margin-left: -15px
+        }
+
+        .col-md-1,
+        .col-md-10,
+        .col-md-12,
+        .col-sm-2,
+        .col-sm-6,
+        .col-xs-3,
+        .col-xs-7 {
+            position: relative;
+            min-height: 1px;
+            padding-right: 15px;
+            padding-left: 15px
+        }
+
+        .col-xs-3,
+        .col-xs-7 {
+            float: left
+        }
+
+        .col-xs-7 {
+            width: 58.33333333%
+        }
+
+        .col-xs-3 {
+            width: 25%
+        }
+
+        @media (min-width:768px) {
+
+            .col-sm-2,
+            .col-sm-6 {
+                float: left
+            }
+
+            .col-sm-6 {
+                width: 50%
+            }
+
+            .col-sm-2 {
+                width: 16.66666667%
+            }
+        }
+
+        @media (min-width:992px) {
+
+            .col-md-1,
+            .col-md-10,
+            .col-md-12 {
+                float: left
+            }
+
+            .col-md-12 {
+                width: 100%
+            }
+
+            .col-md-10 {
+                width: 83.33333333%
+            }
+
+            .col-md-1 {
+                width: 8.33333333%
+            }
+        }
+
+        table {
+            background-color: transparent
+        }
+
+        th {
+            text-align: left
+        }
+
+        .table {
+            width: 100%;
+            max-width: 100%;
+            margin-bottom: 20px
+        }
+
+        .table>tbody>tr>td,
+        .table>thead>tr>th {
+            padding: 8px;
+            line-height: 1.42857143;
+            vertical-align: top;
+            border-top: 1px solid #ddd
+        }
+
+        .table>thead>tr>th {
+            vertical-align: bottom;
+            border-bottom: 2px solid #ddd
+        }
+
+        .table>thead:first-child>tr:first-child>th {
+            border-top: 0
+        }
+
+        .table-striped>tbody>tr:nth-of-type(odd) {
+            background-color: #f9f9f9
+        }
+
+        .container:after,
+        .container:before,
+        .row:after,
+        .row:before {
+            display: table;
+            content: " "
+        }
+
+        .container:after,
+        .row:after {
+            clear: both
+        }
+
+        @-ms-viewport {
+            width: device-width
+        }
+
+        h1 {
+            margin-top: 20px
+        }
+
+        * {
+            -webkit-box-sizing: border-box;
+            -moz-box-sizing: border-box;
+            box-sizing: border-box
+        }
+
+        body {
+            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+            font-size: 14px;
+            line-height: 1.42857143;
+            color: #333;
+            background-color: #fff;
+            margin: 0
+        }
+
+        h1 {
+            margin-bottom: 10px;
+            font-family: inherit;
+            font-weight: 500;
+            line-height: 1.1;
+            color: inherit
+        }
+
+        .table {
+            margin-bottom: 20px
+        }
+
+        h1 {
+            font-size: 36px
+        }
+
+        a {
+            color: #337ab7;
+            text-decoration: none
+        }
+
+        a:hover {
+            color: #23527c
+        }
+
+        .container {
+            padding-right: 15px;
+            padding-left: 15px;
+            margin-right: auto;
+            margin-left: auto
+        }
+
+        @media (min-width:768px) {
+            .container {
+                width: 750px
+            }
+        }
+
+        @media (min-width:992px) {
+            .container {
+                width: 970px
+            }
+        }
+
+        @media (min-width:1200px) {
+            .container {
+                width: 1170px
+            }
+        }
+
+        .table {
+            width: 100%;
+            max-width: 100%;
+            background-color: transparent;
+            border-spacing: 0;
+            border-collapse: collapse
+        }
+
+        .table-striped>tbody {
+            background-color: #f9f9f9
+        }
+
+        .table>tbody>tr>td,
+        .table>thead>tr>th {
+            padding: 8px;
+            line-height: 1.42857143;
+            vertical-align: top;
+            border-top: 1px solid #ddd
+        }
+
+        .table>thead>tr>th {
+            border-top: 0;
+            vertical-align: bottom;
+            border-bottom: 2px solid #ddd;
+            text-align: left
+        }
+
+        code {
+            padding: 2px 4px;
+            font-size: 90%;
+            color: #c7254e;
+            background-color: #f9f2f4;
+            border-radius: 4px;
+            font-family: Menlo, Monaco, Consolas, "Courier New", monospace
+        }
+
+        .row {
+            margin-right: -15px;
+            margin-left: -15px
+        }
+
+        .col-md-10 {
+            float: left;
+            position: relative;
+            min-height: 1px;
+            padding-right: 15px;
+            padding-left: 15px
+        }
+
+        .col-md-10 {
+            width: 83.33333333%
+        }
+
+        img {
+            vertical-align: middle;
+            border: 0
+        }
+
+        .container {
+            min-width: 320px
+        }
+
+        body {
+            font-family: helvetica
+        }
+
+        table {
+            font-size: 14px
+        }
+
+        table.table tbody tr td {
+            border-top: 0;
+            padding: 10px
+        }
+
+        pre {
+            white-space: pre-line;
+            margin-bottom: 10px;
+            max-height: 275px;
+            overflow: scroll
+        }
+
+        header {
+            background-color: #ed605e;
+            padding: 10px 20px;
+            box-sizing: border-box
+        }
+
+        .logo img {
+            width: 80px
+        }
+
+        .titles h1 {
+            font-size: 30px;
+            font-weight: 300;
+            color: #fff;
+            margin: 24px 0
+        }
+
+        .content h3 {
+            color: gray;
+            margin: 25px 0
+        }
+
+        .foot {
+            padding: 5px 0 20px;
+            text-align: right;
+            color: #c5c5c5;
+            font-weight: 300
+        }
+
+        .foot a {
+            color: #8b8b8b;
+            text-decoration: underline
+        }
+
+        .centered {
+            text-align: center
+        }
+
+        @media all and (max-width:500px) {
+            .titles h1 {
+                font-size: 25px;
+                margin: 26px 0
+            }
+        }
+
+        @media all and (max-width:530px) {
+            .titles h1 {
+                font-size: 20px;
+                margin: 24px 0
+            }
+
+            .logo {
+                padding: 0
+            }
+
+            .logo img {
+                width: 100%;
+                max-width: 80px
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <header>
+        <div class="container">
+            <div class="row">
+                <div class="col-md-1 col-sm-2 col-xs-3 logo">
+                    <a href="/"><img src="https://gobuffalo.io/assets/images/logo_med.png" alt=""></a>
+                </div>
+                <div class="col-md-10 col-sm-6 col-xs-7 titles">
+                    <h1>
+                        <%= status %> - ERROR!
+                    </h1>
+                </div>
+            </div>
+        </div>
+    </header>
+
+    <div class="container content">
+        <div class="row">
+            <div class="col-md-12">
+                <h3>Error Trace</h3>
+                <pre><%= error %></pre>
+
+                <h3>Context</h3>
+                <pre><%= inspect(context) %></pre>
+
+                <h3>Parameters</h3>
+                <pre><%= inspect(params) %></pre>
+
+                <h3>Headers</h3>
+                <pre><%= inspect(headers) %></pre>
+
+                <h3>Form</h3>
+                <pre><%= inspect(posted_form) %></pre>
+
+                <h3>Routes</h3>
+                <table class="table table-striped">
+                    <thead>
+                        <tr text-align="left">
+                            <th class="centered">METHOD</th>
+                            <th>PATH</th>
+                            <th>NAME</th>
+                            <th>HANDLER</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+
+                        <%= for (r) in routes { %>
+                            <tr>
+                                <td class="centered">
+                                    <%= r.Method %>
+                                </td>
+                                <td>
+                                    <%= if (r.Method !="GET" || r.Path ~="{" ) { %>
+                                        <%= r.Path %>
+                                            <% } else { %>
+                                                <a href="<%= r.Path %>">
+                                                    <%= r.Path %>
+                                                </a>
+                                                <% } %>
+                                </td>
+                                <td>
+                                    <%= r.PathName %>
+                                </td>
+                                <td><code><%= r.HandlerName %></code></td>
+                            </tr>
+                            <% } %>
+
+                    </tbody>
+                </table>
+            </div>
+        </div>
+        <div class="foot"> <span> Powered by <a href="http://gobuffalo.io/">gobuffalo.io</a></span></div>
+    </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/error.prod.html b/error.prod.html
new file mode 100644
index 00000000..091a176e
--- /dev/null
+++ b/error.prod.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        h1,
+        p.powered {
+            text-align: center
+        }
+
+        body {
+            background: #ECECEC;
+            padding-top: 25px;
+            font-family: helvetica neue, helvetica, sans-serif;
+            color: #333
+        }
+
+        .card {
+            box-sizing: border-box;
+            width: 440px;
+            min-width: 270px;
+            margin: 0 auto;
+            padding: 10px 25px 35px 10px;
+            background: #FFF;
+            box-shadow: 0 2px 4px 0 rgba(185, 185, 185, .28);
+            border-radius: 5px
+        }
+
+        .card p {
+            max-width: 320px;
+            margin: 15px auto
+        }
+
+        h1 {
+            font-size: 22px
+        }
+
+        hr {
+            border: .5px solid #D72727;
+            width: 180px
+        }
+
+        p.powered {
+            font-family: HelveticaNeue-Light;
+            font-size: 12px;
+            color: #333
+        }
+
+        @media (max-width:600px) {
+            .card {
+                width: 100%;
+                display: block
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container">
+        <div class="card">
+            <h1>We're Sorry!</h1>
+            <hr>
+            <p>It looks like something went wrong! Don't worry, we are aware of the problem and are looking into it.</p>
+            <p>Sorry if this has caused you any problems. Please check back again later.</p>
+        </div>
+
+        <p class="powered">powered by <a href="https://gobuffalo.io">gobuffalo.io</a></p>
+    </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/error_templates.go b/error_templates.go
index 63f82b87..2fb4d13d 100644
--- a/error_templates.go
+++ b/error_templates.go
@@ -1,125 +1,16 @@
 package buffalo
 
-var devErrorTmpl = `
-<html>
-<head>
-  <title><%= status %> - ERROR!</title>
-  <style>html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}header{display:block}a{background-color:transparent}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}img{border:0}pre{overflow:auto}code,pre{font-family:monospace,monospace;font-size:1em}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h3{orphans:3;widows:3}h3{page-break-after:avoid}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}h1,h3{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1,h3{margin-top:20px;margin-bottom:10px}h1{font-size:36px}h3{font-size:24px}code,pre{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.row{margin-right:-15px;margin-left:-15px}.col-md-1,.col-md-10,.col-md-12,.col-sm-2,.col-sm-6,.col-xs-3,.col-xs-7{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-3,.col-xs-7{float:left}.col-xs-7{width:58.33333333%}.col-xs-3{width:25%}@media (min-width:768px){.col-sm-2,.col-sm-6{float:left}.col-sm-6{width:50%}.col-sm-2{width:16.66666667%}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-12{float:left}.col-md-12{width:100%}.col-md-10{width:83.33333333%}.col-md-1{width:8.33333333%}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>thead:first-child>tr:first-child>th{border-top:0}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.container:after,.container:before,.row:after,.row:before{display:table;content:" "}.container:after,.row:after{clear:both}@-ms-viewport{width:device-width}
-	h1{margin-top:20px}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff;margin:0}h1{margin-bottom:10px;font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.table{margin-bottom:20px}h1{font-size:36px}a{color:#337ab7;text-decoration:none}a:hover{color:#23527c}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.table{width:100%;max-width:100%;background-color:transparent;border-spacing:0;border-collapse:collapse}.table-striped>tbody{background-color:#f9f9f9}.table>tbody>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{border-top:0;vertical-align:bottom;border-bottom:2px solid #ddd;text-align:left}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace}.row{margin-right:-15px;margin-left:-15px}.col-md-10{float:left;position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-md-10{width:83.33333333%}img{vertical-align:middle;border:0}.container{min-width:320px}body{font-family:helvetica}table{font-size:14px}table.table tbody tr td{border-top:0;padding:10px}pre{white-space:pre-line;margin-bottom:10px;max-height:275px;overflow:scroll}header{background-color:#ed605e;padding:10px 20px;box-sizing:border-box}.logo img{width:80px}.titles h1{font-size:30px;font-weight:300;color:#fff;margin:24px 0}.content h3{color:gray;margin:25px 0}.foot{padding:5px 0 20px;text-align:right;color:#c5c5c5;font-weight:300}.foot a{color:#8b8b8b;text-decoration:underline}.centered{text-align:center}@media all and (max-width:500px){.titles h1{font-size:25px;margin:26px 0}}@media all and (max-width:530px){.titles h1{font-size:20px;margin:24px 0}.logo{padding:0}.logo img{width:100%;max-width:80px}}
-  </style>
-</head>
+import (
+	_ "embed"
+)
 
-<body>
-  <header>
-    <div class="container">
-      <div class="row">
-        <div class="col-md-1 col-sm-2 col-xs-3 logo">
-          <a href="/"><img src="https://gobuffalo.io/assets/images/logo_med.png" alt=""></a>
-        </div>
-        <div class="col-md-10 col-sm-6 col-xs-7 titles">
-          <h1>
-            <%= status %> - ERROR!
-          </h1>
-        </div>
-      </div>
-    </div>
-  </header>
+var (
+	//go:embed error.dev.html
+	devErrorTmpl string
 
-  <div class="container content">
-    <div class="row">
-      <div class="col-md-12">
-        <h3>Error Trace</h3>
-        <pre><%= error %></pre>
+	//go:embed error.prod.html
+	prodErrorTmpl string
 
-        <h3>Context</h3>
-        <pre><%= inspect(context) %></pre>
-
-        <h3>Parameters</h3>
-        <pre><%= inspect(params) %></pre>
-
-        <h3>Headers</h3>
-        <pre><%= inspect(headers) %></pre>
-
-        <h3>Form</h3>
-        <pre><%= inspect(posted_form) %></pre>
-
-        <h3>Routes</h3>
-        <table class="table table-striped">
-          <thead>
-            <tr text-align="left">
-              <th class="centered">METHOD</th>
-              <th>PATH</th>
-              <th>NAME</th>
-              <th>HANDLER</th>
-            </tr>
-          </thead>
-          <tbody>
-
-            <%= for (r) in routes { %>
-              <tr>
-                <td class="centered">
-                  <%= r.Method %>
-                </td>
-                <td>
-                  <%= if (r.Method != "GET" || r.Path ~= "{") { %>
-                    <%= r.Path %>
-                  <% } else { %>
-                    <a href="<%= r.Path %>"><%= r.Path %></a>
-                  <% } %>
-                </td>
-                <td>
-                  <%= r.PathName %>
-                </td>
-                <td><code><%= r.HandlerName %></code></td>
-              </tr>
-            <% } %>
-
-          </tbody>
-        </table>
-      </div>
-    </div>
-    <div class="foot"> <span> Powered by <a href="http://gobuffalo.io/">gobuffalo.io</a></span></div>
-  </div>
-</body>
-</html>
-`
-var prodErrorTmpl = `
-<!DOCTYPE html>
-<html>
-<head>
-<style>h1,p.powered{text-align:center}body{background:#ECECEC;padding-top:25px;font-family:helvetica neue,helvetica,sans-serif;color:#333}.card{box-sizing:border-box;width:440px;min-width:270px;margin:0 auto;padding:10px 25px 35px 10px;background:#FFF;box-shadow:0 2px 4px 0 rgba(185,185,185,.28);border-radius:5px}.card p{max-width:320px;margin:15px auto}h1{font-size:22px}hr{border:.5px solid #D72727;width:180px}p.powered{font-family:HelveticaNeue-Light;font-size:12px;color:#333}@media (max-width:600px){.card{width:100%;display:block}}</style>
-</head>
-<body>
-<div class="container">
-	<div class="card">
-		<h1>We're Sorry!</h1>
-		<hr>
-		<p>It looks like something went wrong! Don't worry, we are aware of the problem and are looking into it.</p>
-		<p>Sorry if this has caused you any problems. Please check back again later.</p>
-	</div>
-
-	<p class="powered">powered by <a href="https://gobuffalo.io">gobuffalo.io</a></p>
-</div>
-</body>
-</html>
-`
-
-var prodNotFoundTmpl = `
-<!DOCTYPE html>
-<html>
-<head>
-<style>h1,p.powered{text-align:center}body{background:#ECECEC;padding-top:25px;font-family:helvetica neue,helvetica,sans-serif;color:#333}.card{box-sizing:border-box;width:440px;min-width:270px;margin:0 auto;padding:10px 25px 35px 10px;background:#FFF;box-shadow:0 2px 4px 0 rgba(185,185,185,.28);border-radius:5px}.card p{max-width:320px;margin:15px auto}h1{font-size:22px}hr{border:.5px solid #1272E2;width:180px}p.powered{font-family:HelveticaNeue-Light;font-size:12px;color:#333}@media (max-width:600px){.card{width:100%;display:block}}</style>
-</head>
-<body>
-<div class="container">
-	<div class="card">
-		<h1>Not Found</h1>
-		<hr>
-		<p>The page you're looking for does not exist, you may have mistyped the address or the page may have been moved.</p>
-	</div>
-
-	<p class="powered">powered by <a href="https://gobuffalo.io">gobuffalo.io</a></p>
-</div>
-</body>
-</html>
-`
+	//go:embed notfound.prod.html
+	prodNotFoundTmpl string
+)
diff --git a/flash.go b/flash.go
index 7e5a773c..ecf08d17 100644
--- a/flash.go
+++ b/flash.go
@@ -39,7 +39,6 @@ func (f Flash) Add(key, value string) {
 func (f Flash) persist(session *Session) {
 	b, _ := json.Marshal(f.data)
 	session.Set(flashKey, b)
-	session.Save()
 }
 
 //newFlash creates a new Flash and loads the session data inside its data.
diff --git a/notfound.prod.html b/notfound.prod.html
new file mode 100644
index 00000000..3c1ab654
--- /dev/null
+++ b/notfound.prod.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <style>
+        h1,
+        p.powered {
+            text-align: center
+        }
+
+        body {
+            background: #ECECEC;
+            padding-top: 25px;
+            font-family: helvetica neue, helvetica, sans-serif;
+            color: #333
+        }
+
+        .card {
+            box-sizing: border-box;
+            width: 440px;
+            min-width: 270px;
+            margin: 0 auto;
+            padding: 10px 25px 35px 10px;
+            background: #FFF;
+            box-shadow: 0 2px 4px 0 rgba(185, 185, 185, .28);
+            border-radius: 5px
+        }
+
+        .card p {
+            max-width: 320px;
+            margin: 15px auto
+        }
+
+        h1 {
+            font-size: 22px
+        }
+
+        hr {
+            border: .5px solid #1272E2;
+            width: 180px
+        }
+
+        p.powered {
+            font-family: HelveticaNeue-Light;
+            font-size: 12px;
+            color: #333
+        }
+
+        @media (max-width:600px) {
+            .card {
+                width: 100%;
+                display: block
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container">
+        <div class="card">
+            <h1>Not Found</h1>
+            <hr>
+            <p>The page you're looking for does not exist, you may have mistyped the address or the page may have been
+                moved.</p>
+        </div>
+
+        <p class="powered">powered by <a href="https://gobuffalo.io">gobuffalo.io</a></p>
+    </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/options.go b/options.go
index 3b180bdc..ebaa963c 100644
--- a/options.go
+++ b/options.go
@@ -57,7 +57,7 @@ type Options struct {
 	// PreHandlers are http.Handlers that are called between the http.Server
 	// and the buffalo Application.
 	PreHandlers []http.Handler `json:"-"`
-	// PreWare takes an http.Handler and returns and http.Handler
+	// PreWare takes an http.Handler and returns an http.Handler
 	// and acts as a pseudo-middleware between the http.Server and
 	// a Buffalo application.
 	PreWares []PreWare `json:"-"`
@@ -73,7 +73,7 @@ type Options struct {
 	cancel context.CancelFunc
 }
 
-// PreWare takes an http.Handler and returns and http.Handler
+// PreWare takes an http.Handler and returns an http.Handler
 // and acts as a pseudo-middleware between the http.Server and
 // a Buffalo application.
 type PreWare func(http.Handler) http.Handler
diff --git a/request_logger.go b/request_logger.go
index 0ceaf786..17f2be18 100644
--- a/request_logger.go
+++ b/request_logger.go
@@ -42,7 +42,6 @@ func RequestLoggerFunc(h Handler) Handler {
 			}
 			irid = rs
 			c.Session().Set("requestor_id", irid)
-			c.Session().Save()
 		}
 
 		rid := irid.(string) + "-" + rs
diff --git a/route_info.go b/route_info.go
index 8bb849ce..b5e20d51 100644
--- a/route_info.go
+++ b/route_info.go
@@ -103,7 +103,6 @@ func (ri RouteInfo) ServeHTTP(res http.ResponseWriter, req *http.Request) {
 
 	events.EmitPayload(EvtRouteStarted, payload)
 	err := a.Middleware.handler(ri)(c)
-	c.Flash().persist(c.Session())
 
 	if err != nil {
 		status := http.StatusInternalServerError
diff --git a/runtime/version.go b/runtime/version.go
index c6eafa10..4bf2cc5c 100644
--- a/runtime/version.go
+++ b/runtime/version.go
@@ -1,4 +1,4 @@
 package runtime
 
 // Version is the current version of the buffalo binary
-var Version = "v0.18.2"
+var Version = "v0.18.3"
diff --git a/session.go b/session.go
index 9bfec880..f49ea60b 100644
--- a/session.go
+++ b/session.go
@@ -63,13 +63,3 @@ func (a *App) getSession(r *http.Request, w http.ResponseWriter) *Session {
 		res:     w,
 	}
 }
-
-func sessionSaver(next Handler) Handler {
-	return func(c Context) error {
-		err := next(c)
-		if err != nil {
-			return err
-		}
-		return c.Session().Save()
-	}
-}


Test patch:
diff --git a/router_test.go b/router_test.go
index 1e3fa525..ff3584fa 100644
--- a/router_test.go
+++ b/router_test.go
@@ -311,15 +311,15 @@ func Test_Router_Group_Middleware(t *testing.T) {
 
 	a := testApp()
 	a.Use(func(h Handler) Handler { return h })
-	r.Len(a.Middleware.stack, 5)
+	r.Len(a.Middleware.stack, 4)
 
 	g := a.Group("/api/v1")
-	r.Len(a.Middleware.stack, 5)
-	r.Len(g.Middleware.stack, 5)
+	r.Len(a.Middleware.stack, 4)
+	r.Len(g.Middleware.stack, 4)
 
 	g.Use(func(h Handler) Handler { return h })
-	r.Len(a.Middleware.stack, 5)
-	r.Len(g.Middleware.stack, 6)
+	r.Len(a.Middleware.stack, 4)
+	r.Len(g.Middleware.stack, 5)
 }
 
 func Test_Router_Redirect(t *testing.T) {
diff --git a/session_test.go b/session_test.go
new file mode 100644
index 00000000..b69d241a
--- /dev/null
+++ b/session_test.go
@@ -0,0 +1,64 @@
+package buffalo
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/gobuffalo/buffalo/render"
+	"github.com/gobuffalo/httptest"
+
+	"github.com/stretchr/testify/require"
+)
+
+func Test_Session_SingleCookie(t *testing.T) {
+	r := require.New(t)
+
+	sessionName := "_test_session"
+	a := New(Options{SessionName: sessionName})
+	rr := render.New(render.Options{})
+
+	a.GET("/", func(c Context) error {
+		return c.Render(http.StatusCreated, rr.String(""))
+	})
+
+	w := httptest.New(a)
+	res := w.HTML("/").Get()
+
+	var sessionCookies []string
+	for _, c := range res.Header().Values("Set-Cookie") {
+		if strings.HasPrefix(c, sessionName) {
+			sessionCookies = append(sessionCookies, c)
+		}
+	}
+
+	r.Equal(1, len(sessionCookies))
+}
+
+func Test_Session_CustomValue(t *testing.T) {
+	r := require.New(t)
+
+	a := New(Options{})
+	rr := render.New(render.Options{})
+
+	// Root path sets a custom session value
+	a.GET("/", func(c Context) error {
+		c.Session().Set("example", "test")
+		return c.Render(http.StatusCreated, rr.String(""))
+	})
+	// /session path prints custom session value as response
+	a.GET("/session", func(c Context) error {
+		sessionValue := c.Session().Get("example")
+		return c.Render(http.StatusCreated, rr.String(fmt.Sprintf("%s", sessionValue)))
+	})
+
+	w := httptest.New(a)
+	_ = w.HTML("/").Get()
+
+	// Create second request that should contain the cookie from the first response
+	reqGetSession := w.HTML("/session")
+	resGetSession := reqGetSession.Get()
+
+	r.Equal(resGetSession.Body.String(), "test")
+}