5.4 KiB
Adding a New Page — Step by Step
Step 1 — Choose a layout
Declare the layout in your template's very first line using a Go comment:
{{/* layout: base_public.html */}}
If you omit this line, base.html (sidebar layout) is used automatically.
Built-in layouts:
| Layout file | Navigation | Best for |
|---|---|---|
base.html |
Left sidebar | Authenticated app pages (dashboard, profile, etc.) |
base_public.html |
Top bar | Public/marketing pages, landing pages |
base_bare.html |
None | Minimal pages, print view, embeds |
Custom layout: Create web/templates/layouts/my_layout.html, then declare {{/* layout: my_layout.html */}} in your page. Any layout filename works as long as it lives in the layouts/ directory and contains {{define "base"}} ... {{end}}.
Step 2 — Create the template
web/templates/pages/mypage.html:
{{/* layout: base.html */}}
{{template "base" .}}
{{define "title"}}My Page — GoApp{{end}}
{{define "content"}}
<div class="max-w-4xl mx-auto">
<h2 class="text-2xl font-semibold text-white mb-6">{{.Title}}</h2>
{{range .Items}}
<div class="card">
<p class="text-gray-300">{{.}}</p>
</div>
{{end}}
</div>
{{end}}
{{/* Optional: page-specific <head> additions */}}
{{define "head"}}<style>/* page CSS */</style>{{end}}
{{/* Optional: page-specific scripts */}}
{{define "scripts"}}<script>/* page JS */</script>{{end}}
The {{/* layout: ... */}} comment is read only by Go at startup — it never appears in the HTML output.
Step 3 — Create the handler
internal/handlers/mypage.go:
package handlers
import "net/http"
// MyPageData holds everything the template needs.
// Always embed PageData — it carries User, Nav, flash messages, etc.
type MyPageData struct {
PageData
Items []string // your page-specific data
}
type MyPageHandler struct {
tmpl *Renderer
// Add other dependencies here: *db.DB, *mailer.Mailer, *logger.Logger
}
func NewMyPageHandler(r *Renderer) *MyPageHandler {
return &MyPageHandler{tmpl: r}
}
func (h *MyPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
data := MyPageData{
PageData: NewPageData(r, "My Page"),
Items: []string{"hello", "world"},
}
h.tmpl.Render(w, "mypage", data)
}
Step 4 — Register the route
Add one line to internal/router/router.go:
// Public page (no login required):
mux.Handle("/mypage", handlers.NewMyPageHandler(renderer))
// Authenticated page (redirects to /login if not signed in):
mux.Handle("/mypage", middleware.RequireAuth(handlers.NewMyPageHandler(renderer)))
// Admin-only page:
mux.Handle("/mypage", adminMW(handlers.NewMyPageHandler(renderer)))
Step 5 — (Optional) Add to sidebar navigation
In internal/handlers/renderer.go, append to DefaultNav:
var DefaultNav = []NavItem{
// existing items ...
{Label: "My Page", Href: "/mypage", Icon: "info"}, // all users
{Label: "Admin Page", Href: "/mypage", Icon: "shield", AdminOnly: true}, // admins only
}
Available icon names: home, info, mail, users, activity, settings, logout, shield, key, mfa
Email — Using the Mailer
Configure in data/config.json
"email": {
"enabled": true,
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"encryption": "starttls",
"auth": true,
"username": "you@gmail.com",
"password": "your-app-password",
"from_address": "GoApp <you@gmail.com>"
}
Gmail tip: Create an App Password (not your main password). Requires 2FA enabled on your Google account.
Wire the Mailer into a handler
// In router.go (m is already created from cfg.Email):
mux.Handle("/notify", handlers.NewNotifyHandler(renderer, m, log))
// In your handler:
type NotifyHandler struct {
tmpl *Renderer
mailer *mailer.Mailer
log *logger.Logger
}
func NewNotifyHandler(r *Renderer, m *mailer.Mailer, l *logger.Logger) *NotifyHandler {
return &NotifyHandler{tmpl: r, mailer: m, log: l}
}
Send plain-text email
err := h.mailer.Send(mailer.Message{
To: []string{"user@example.com"},
Subject: "Welcome to GoApp",
Body: "Hello! Your account is ready.",
})
if err != nil {
h.log.Warn("send welcome email", "err", err)
// Don't abort — email failure should not break the UX
}
Send HTML email
err := h.mailer.Send(mailer.Message{
To: []string{"user@example.com"},
Subject: "Welcome to GoApp",
Body: "<h1>Welcome!</h1><p>Your account is <strong>ready</strong>.</p>",
IsHTML: true,
})
Multiple recipients + CC
err := h.mailer.Send(mailer.Message{
To: []string{"alice@example.com", "bob@example.com"},
CC: []string{"manager@example.com"},
Subject: "Team notification",
Body: "Something happened that you should know about.",
})
Safe error handling pattern
When email.enabled is false, Send() returns nil immediately — the rest of your handler works normally. Always log email errors rather than aborting the request:
if err := h.mailer.Send(msg); err != nil {
h.log.Warn("email send failed", "err", err)
// continue — don't return an error page to the user
}