Building a Server-Rendered Dev.to scraper : HTML Templating, API Integration, and Pagination
Click to watch the companion tutorial video Why Server-Side Rendering (SSR) Matters In today's web development landscape dominated by client-side frameworks, server-side rendering offers compelling advantages: Instant Page Loads: HTML arrives ready-to-display SEO Superiority: Search engines easily crawl content Simplified Architecture: Single codebase manages both API and UI Resource Efficiency: Reduced client-side JavaScript overhead GoFr, an opinionated Go framework, makes SSR implementation remarkably straightforward. Let's build a Dev.to article reader that demonstrates these principles in action. Project Setup: Laying the Foundation 1. Initializing the GoFr Application (main.go) package main import ( "encoding/json" "gofr.dev/pkg/gofr" "strconv" ) type Article struct { Title string `json:"title"` URL string `json:"url"` Description string `json:"description"` } type PageData struct { Articles []Article Tag string Page int } func main() { // Initialize GoFr application app := gofr.New() // Configure Dev.to API service app.AddHTTPService("dev-articles", "https://dev.to/api") // Register route handler app.GET("/dev-articles", FetchArticles) // Serve static files (CSS, templates) app.AddStaticFiles("/", "./static") // Start the server app.Run() } Key Components Explained: Service Configuration:AddHTTPService creates a pre-configured HTTP client for the Dev.to API, handling connection pooling and timeouts automatically. Route Handling:The GET method associates the /dev-articles endpoint with our FetchArticles handler. Static Assets:AddStaticFiles serves CSS and templates from the static directory, essential for our SSR approach. Core Business Logic: The Article Handler 2. Implementing the FetchArticles Handler func FetchArticles(ctx *gofr.Context) (any, error) { // Get search parameters with defaults tag := ctx.Param("tag") if tag == "" { tag = "go" // Default to Go articles } page, _ := strconv.Atoi(ctx.Param("page")) if page

Click to watch the companion tutorial video
Why Server-Side Rendering (SSR) Matters
In today's web development landscape dominated by client-side frameworks, server-side rendering offers compelling advantages:
- Instant Page Loads: HTML arrives ready-to-display
- SEO Superiority: Search engines easily crawl content
- Simplified Architecture: Single codebase manages both API and UI
- Resource Efficiency: Reduced client-side JavaScript overhead
GoFr, an opinionated Go framework, makes SSR implementation remarkably straightforward. Let's build a Dev.to article reader that demonstrates these principles in action.
Project Setup: Laying the Foundation
1. Initializing the GoFr Application (main.go
)
package main
import (
"encoding/json"
"gofr.dev/pkg/gofr"
"strconv"
)
type Article struct {
Title string `json:"title"`
URL string `json:"url"`
Description string `json:"description"`
}
type PageData struct {
Articles []Article
Tag string
Page int
}
func main() {
// Initialize GoFr application
app := gofr.New()
// Configure Dev.to API service
app.AddHTTPService("dev-articles", "https://dev.to/api")
// Register route handler
app.GET("/dev-articles", FetchArticles)
// Serve static files (CSS, templates)
app.AddStaticFiles("/", "./static")
// Start the server
app.Run()
}
Key Components Explained:
Service Configuration:
AddHTTPService
creates a pre-configured HTTP client for the Dev.to API, handling connection pooling and timeouts automatically.Route Handling:The
GET
method associates the/dev-articles
endpoint with ourFetchArticles
handler.Static Assets:
AddStaticFiles
serves CSS and templates from thestatic
directory, essential for our SSR approach.
Core Business Logic: The Article Handler
2. Implementing the FetchArticles Handler
func FetchArticles(ctx *gofr.Context) (any, error) {
// Get search parameters with defaults
tag := ctx.Param("tag")
if tag == "" {
tag = "go" // Default to Go articles
}
page, _ := strconv.Atoi(ctx.Param("page"))
if page < 1 {
page = 1 // Ensure minimum page number
}
// Fetch articles from Dev.to API
service := ctx.GetHTTPService("dev-articles")
resp, err := service.Get(ctx, "/articles", map[string]interface{}{
"tag": tag,
"page": page,
"per_page": 4, // Optimal for initial load
})
if err != nil {
return nil, err // Handle API errors
}
defer resp.Body.Close()
// Parse API response
var articles []Article
if err := json.NewDecoder(resp.Body).Decode(&articles); err != nil {
return nil, err // Handle parsing errors
}
// Render template with data
return gofr.Template{
Data: PageData{articles, tag, page},
Name: "devTo.html",
}, nil
}
Architectural Decisions:
-
Parameter Handling
- Default values ensure consistent behavior
- Type conversion guards against invalid inputs
-
per_page=4
balances content density and performance
-
Error Handling
- Automatic error propagation through GoFr's middleware
- Clean separation of concerns between API and rendering
-
Service Abstraction
- HTTP service configuration centralized in
main.go
- Easy to swap API endpoints or add caching later
- HTTP service configuration centralized in
Presentation Layer: HTML Templating
3. Template Implementation (static/devTo.html
)
lang="en">
charset="UTF-8">
Go Articles from Dev.to
rel="stylesheet" href="/style.css">
// Hybrid pagination: Client-side navigation with server-side validation
function updatePage(delta) {
const params = new URLSearchParams(window.location.search)
let page = parseInt(params.get('page') || {{.Page}}
let tag = params.get('tag') || '{{.Tag}}'
page = Math.max(1, page + delta)
window.location.href = `/dev-articles?tag=${tag}&page=${page}`
}
id="content">