home / skills / aj-geddes / useful-ai-prompts / server-side-rendering

server-side-rendering skill

/skills/server-side-rendering

This skill helps you implement server-side rendering across Python, Node.js, and Ruby frameworks with templating, routing, and performance optimization.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill server-side-rendering

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
17.5 KB
---
name: server-side-rendering
description: Implement server-side rendering with template engines, view layers, and dynamic content generation. Use when building server-rendered applications, implementing MVC architectures, and generating HTML on the server.
---

# Server-Side Rendering

## Overview

Build server-side rendered applications using modern template engines, view layers, and data-driven HTML generation with caching, streaming, and performance optimization across Python, Node.js, and Ruby frameworks.

## When to Use

- Building traditional web applications
- Rendering HTML on the server
- Implementing SEO-friendly applications
- Creating real-time updating pages
- Building admin dashboards
- Implementing email templates

## Instructions

### 1. **Flask with Jinja2 Templates**

```python
# app.py
from flask import Flask, render_template, request, jsonify
from datetime import datetime

app = Flask(__name__)

# Custom Jinja2 filters
@app.template_filter('currency')
def format_currency(value):
    return f"${value:.2f}"

@app.template_filter('date_format')
def format_date(date_obj):
    return date_obj.strftime('%Y-%m-%d %H:%M:%S')

@app.context_processor
def inject_globals():
    """Inject global variables into templates"""
    return {
        'app_name': 'My App',
        'current_year': datetime.now().year,
        'support_email': '[email protected]'
    }

# routes.py
@app.route('/')
def index():
    """Home page"""
    featured_posts = Post.query.filter_by(featured=True).limit(5).all()
    return render_template('index.html', featured_posts=featured_posts)

@app.route('/dashboard')
@login_required
def dashboard():
    """User dashboard"""
    user_stats = {
        'total_posts': current_user.posts.count(),
        'total_views': sum(p.view_count for p in current_user.posts),
        'total_followers': current_user.followers.count()
    }

    recent_activity = current_user.get_activity(limit=10)

    return render_template(
        'dashboard.html',
        stats=user_stats,
        activity=recent_activity
    )

@app.route('/posts/<slug>')
def view_post(slug):
    """View single post"""
    post = Post.query.filter_by(slug=slug).first_or_404()

    # Increment view count
    post.view_count += 1
    db.session.commit()

    # Get related posts
    related = Post.query.filter(
        Post.category_id == post.category_id,
        Post.id != post.id
    ).limit(5).all()

    return render_template(
        'post.html',
        post=post,
        related_posts=related,
        comments=post.comments.order_by(Comment.created_at.desc()).all()
    )

@app.route('/search')
def search():
    """Search posts"""
    query = request.args.get('q', '')
    page = request.args.get('page', 1, type=int)

    if not query:
        return render_template('search.html', posts=[], query='')

    posts = Post.query.filter(
        Post.title.ilike(f'%{query}%') |
        Post.content.ilike(f'%{query}%')
    ).paginate(page=page, per_page=20)

    return render_template(
        'search.html',
        posts=posts.items,
        total=posts.total,
        query=query,
        page=page
    )

@app.route('/admin/posts/create', methods=['GET', 'POST'])
@login_required
@admin_required
def create_post():
    """Create new post"""
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']
        category_id = request.form['category_id']

        post = Post(
            title=title,
            slug=generate_slug(title),
            content=content,
            category_id=category_id,
            author_id=current_user.id
        )
        db.session.add(post)
        db.session.commit()

        return redirect(url_for('view_post', slug=post.slug))

    categories = Category.query.all()
    return render_template('admin/create_post.html', categories=categories)
```

### 2. **Jinja2 Template Examples**

```html
<!-- base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{{ app_name }}{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    {% block extra_head %}{% endblock %}
</head>
<body>
    <nav class="navbar">
        <div class="container">
            <h1>{{ app_name }}</h1>
            <ul>
                <li><a href="{{ url_for('index') }}">Home</a></li>
                {% if current_user.is_authenticated %}
                    <li><a href="{{ url_for('dashboard') }}">Dashboard</a></li>
                    <li><a href="{{ url_for('logout') }}">Logout</a></li>
                {% else %}
                    <li><a href="{{ url_for('login') }}">Login</a></li>
                    <li><a href="{{ url_for('register') }}">Register</a></li>
                {% endif %}
            </ul>
        </div>
    </nav>

    <main class="container">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }}">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; {{ current_year }} {{ app_name }}. All rights reserved.</p>
    </footer>

    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
    {% block extra_scripts %}{% endblock %}
</body>
</html>

<!-- dashboard.html -->
{% extends "base.html" %}

{% block title %}Dashboard - {{ app_name }}{% endblock %}

{% block content %}
<div class="dashboard">
    <h1>Welcome, {{ current_user.first_name }}!</h1>

    <div class="stats-grid">
        <div class="stat-card">
            <h3>Total Posts</h3>
            <p class="stat-value">{{ stats.total_posts }}</p>
        </div>
        <div class="stat-card">
            <h3>Total Views</h3>
            <p class="stat-value">{{ stats.total_views | default(0) }}</p>
        </div>
        <div class="stat-card">
            <h3>Followers</h3>
            <p class="stat-value">{{ stats.total_followers }}</p>
        </div>
    </div>

    <section class="recent-activity">
        <h2>Recent Activity</h2>
        {% if activity %}
            <ul class="activity-list">
                {% for item in activity %}
                    <li>
                        <span class="activity-date">{{ item.created_at | date_format }}</span>
                        <span class="activity-text">{{ item.description }}</span>
                    </li>
                {% endfor %}
            </ul>
        {% else %}
            <p>No recent activity.</p>
        {% endif %}
    </section>
</div>
{% endblock %}

<!-- post.html -->
{% extends "base.html" %}

{% block title %}{{ post.title }} - {{ app_name }}{% endblock %}

{% block content %}
<article class="post">
    <header class="post-header">
        <h1>{{ post.title }}</h1>
        <div class="post-meta">
            <span class="author">By {{ post.author.full_name }}</span>
            <span class="date">{{ post.created_at | date_format }}</span>
            <span class="category">
                <a href="{{ url_for('view_category', slug=post.category.slug) }}">
                    {{ post.category.name }}
                </a>
            </span>
        </div>
    </header>

    <div class="post-content">
        {{ post.content | safe }}
    </div>

    {% if related_posts %}
        <section class="related-posts">
            <h3>Related Posts</h3>
            <div class="posts-grid">
                {% for related in related_posts %}
                    <div class="post-card">
                        <h4><a href="{{ url_for('view_post', slug=related.slug) }}">{{ related.title }}</a></h4>
                        <p>{{ related.excerpt or related.content[:100] }}...</p>
                        <a href="{{ url_for('view_post', slug=related.slug) }}" class="read-more">Read More</a>
                    </div>
                {% endfor %}
            </div>
        </section>
    {% endif %}

    <section class="comments">
        <h3>Comments ({{ comments | length }})</h3>
        {% if comments %}
            <ul class="comment-list">
                {% for comment in comments %}
                    <li class="comment">
                        <strong>{{ comment.author.full_name }}</strong>
                        <time>{{ comment.created_at | date_format }}</time>
                        <p>{{ comment.content }}</p>
                    </li>
                {% endfor %}
            </ul>
        {% else %}
            <p>No comments yet.</p>
        {% endif %}

        {% if current_user.is_authenticated %}
            <form method="POST" action="{{ url_for('add_comment', post_id=post.id) }}" class="comment-form">
                <textarea name="content" placeholder="Add a comment..." required></textarea>
                <button type="submit">Post Comment</button>
            </form>
        {% endif %}
    </section>
</article>
{% endblock %}
```

### 3. **Node.js/Express with EJS Templates**

```javascript
// app.js
const express = require('express');
const path = require('path');

const app = express();

// Set template engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Local variables middleware
app.use((req, res, next) => {
    res.locals.currentUser = req.user || null;
    res.locals.appName = 'My App';
    res.locals.currentYear = new Date().getFullYear();
    next();
});

// Routes
app.get('/', (req, res) => {
    const posts = [
        { id: 1, title: 'Post 1', excerpt: 'First post', slug: 'post-1' },
        { id: 2, title: 'Post 2', excerpt: 'Second post', slug: 'post-2' }
    ];

    res.render('index', { posts });
});

app.get('/posts/:slug', async (req, res) => {
    const { slug } = req.params;
    const post = await Post.findOne({ where: { slug } });

    if (!post) {
        return res.status(404).render('404');
    }

    const comments = await post.getComments();
    const relatedPosts = await Post.findAll({
        where: { categoryId: post.categoryId },
        limit: 5
    });

    res.render('post', {
        post,
        comments,
        relatedPosts
    });
});

app.get('/dashboard', requireAuth, (req, res) => {
    const stats = {
        totalPosts: req.user.posts.length,
        totalViews: req.user.posts.reduce((sum, p) => sum + p.views, 0)
    };

    res.render('dashboard', { stats });
});

app.listen(3000);
```

### 4. **EJS Template Examples**

```html
<!-- views/layout.ejs -->
<!DOCTYPE html>
<html>
<head>
    <title><%= typeof title != 'undefined' ? title + ' - ' : '' %><%= appName %></title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <%- include('partials/navbar') %>

    <main class="container">
        <%- body %>
    </main>

    <%- include('partials/footer') %>

    <script src="/js/main.js"></script>
</body>
</html>

<!-- views/post.ejs -->
<article class="post">
    <h1><%= post.title %></h1>
    <div class="post-meta">
        <span>By <%= post.author.name %></span>
        <span><%= new Date(post.createdAt).toLocaleDateString() %></span>
    </div>

    <div class="post-content">
        <%- post.content %>
    </div>

    <% if (relatedPosts && relatedPosts.length > 0) { %>
        <section class="related-posts">
            <h3>Related Posts</h3>
            <% relatedPosts.forEach(related => { %>
                <div class="post-card">
                    <h4><a href="/posts/<%= related.slug %>"><%= related.title %></a></h4>
                    <p><%= related.excerpt %></p>
                </div>
            <% }); %>
        </section>
    <% } %>

    <section class="comments">
        <h3>Comments (<%= comments.length %>)</h3>

        <% comments.forEach(comment => { %>
            <div class="comment">
                <strong><%= comment.author.name %></strong>
                <time><%= new Date(comment.createdAt).toLocaleDateString() %></time>
                <p><%= comment.content %></p>
            </div>
        <% }); %>

        <% if (currentUser) { %>
            <form method="POST" action="/posts/<%= post.id %>/comments" class="comment-form">
                <textarea name="content" placeholder="Add comment..." required></textarea>
                <button type="submit">Post</button>
            </form>
        <% } %>
    </section>
</article>
```

### 5. **Caching and Performance**

```python
# Flask caching
from flask_caching import Cache

cache = Cache(app, config={'CACHE_TYPE': 'redis'})

@app.route('/posts/<slug>')
@cache.cached(timeout=3600)  # Cache for 1 hour
def view_post(slug):
    """Cached post view"""
    post = Post.query.filter_by(slug=slug).first_or_404()
    comments = post.comments.all()
    return render_template('post.html', post=post, comments=comments)

@app.route('/api/posts')
@cache.cached(timeout=300)  # Cache for 5 minutes
def get_posts():
    """Cached API endpoint"""
    posts = Post.query.filter_by(published=True).all()
    return jsonify([p.to_dict() for p in posts])

# Invalidate cache
@app.route('/admin/posts/<id>/edit', methods=['POST'])
@admin_required
def edit_post(id):
    post = Post.query.get(id)
    # Update post
    db.session.commit()

    # Clear cache
    cache.delete_memoized(view_post, post.slug)
    cache.delete_memoized(get_posts)

    return redirect(url_for('view_post', slug=post.slug))
```

### 6. **Django Template Examples**

```python
# views.py
from django.shortcuts import render
from django.views.generic import DetailView, ListView
from django.db.models import Q
from .models import Post, Comment

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.filter(published=True).order_by('-created_at')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['featured_posts'] = Post.objects.filter(featured=True)[:5]
        return context

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    slug_field = 'slug'

    def get_queryset(self):
        return Post.objects.filter(published=True)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['comments'] = self.object.comments.all()
        context['related_posts'] = Post.objects.filter(
            category=self.object.category
        ).exclude(id=self.object.id)[:5]
        return context
```

### 7. **Django Templates**

```html
<!-- blog/post_list.html -->
{% extends "base.html" %}
{% load custom_filters %}

{% block title %}Blog - {{ app_name }}{% endblock %}

{% block content %}
<div class="blog-section">
    <h1>Blog Posts</h1>

    {% if featured_posts %}
        <section class="featured">
            <h2>Featured Posts</h2>
            <div class="posts-grid">
                {% for post in featured_posts %}
                    <article class="post-card">
                        <h3><a href="{% url 'post-detail' post.slug %}">{{ post.title }}</a></h3>
                        <p>{{ post.excerpt }}</p>
                        <a href="{% url 'post-detail' post.slug %}" class="read-more">Read More</a>
                    </article>
                {% endfor %}
            </div>
        </section>
    {% endif %}

    <section class="posts">
        <h2>All Posts</h2>
        {% for post in posts %}
            <article class="post-item">
                <h3><a href="{% url 'post-detail' post.slug %}">{{ post.title }}</a></h3>
                <div class="meta">
                    <span>By {{ post.author.get_full_name }}</span>
                    <span>{{ post.created_at|date:"M d, Y" }}</span>
                </div>
                <p>{{ post.content|truncatewords:50 }}</p>
            </article>
        {% empty %}
            <p>No posts yet.</p>
        {% endfor %}
    </section>

    {% if is_paginated %}
        <nav class="pagination">
            {% if page_obj.has_previous %}
                <a href="?page=1">First</a>
                <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
            {% endif %}

            <span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>

            {% if page_obj.has_next %}
                <a href="?page={{ page_obj.next_page_number }}">Next</a>
                <a href="?page={{ page_obj.paginator.num_pages }}">Last</a>
            {% endif %}
        </nav>
    {% endif %}
</div>
{% endblock %}
```

## Best Practices

### ✅ DO
- Use template inheritance for DRY code
- Implement caching for frequently rendered pages
- Use template filters for formatting
- Separate concerns between views and templates
- Validate and sanitize all user input
- Use context processors for global variables
- Implement proper pagination
- Use conditional rendering appropriately
- Cache expensive queries
- Optimize template rendering

### ❌ DON'T
- Put business logic in templates
- Use unbounded loops in templates
- Execute database queries in templates
- Trust user input without sanitization
- Over-nest template inheritance
- Use very long template files
- Render sensitive data in templates
- Ignore template caching opportunities
- Use global variables excessively
- Mix multiple concerns in one template

## Complete Example

```python
@app.route('/hello/<name>')
def hello(name):
    return render_template('hello.html', name=name)

# hello.html
<h1>Hello, {{ name | capitalize }}!</h1>
```

Overview

This skill implements server-side rendering using template engines, view layers, and dynamic HTML generation for Python, Node.js, and Ruby ecosystems. It provides patterns for routing, context injection, template composition, caching, and performance optimizations. Use it to build SEO-friendly, accessible server-rendered applications and admin interfaces.

How this skill works

The skill wires server routes to template rendering engines (Jinja2, EJS, Django templates) and passes data models, user context, and helper filters into views. It includes examples for common pages (index, post, dashboard), partials/layouts, and form handling, plus caching middleware and cache invalidation points. Templates demonstrate safe content rendering, pagination, related content lookup, and comment workflows.

When to use it

  • Building traditional multi-page web applications where HTML is rendered on the server
  • Improving SEO and social previews by serving fully rendered HTML
  • Implementing MVC-style architectures with clear separation of controllers, views, and models
  • Creating admin dashboards, email templates, and content-heavy pages
  • Adding caching and streaming to improve first-byte and time-to-interactive performance

Best practices

  • Inject global context (app name, current year, current user) at middleware level to keep templates clean
  • Use template filters/helpers for formatting (dates, currency) to centralize presentation logic
  • Cache rendered pages and API responses, and provide targeted cache invalidation on content updates
  • Escape or mark content safe explicitly; prefer server-side sanitization for user-generated HTML
  • Paginate and lazy-load heavy lists; use related-content queries with limits to avoid N+1 queries

Example use cases

  • A blog platform that renders SEO-friendly post pages with related posts and comments
  • An admin dashboard showing user stats and recent activity with server-side pagination
  • A product catalog that renders prices and availability with formatted filters and cached product pages
  • Email template rendering on the server with the same templates used for web views
  • A search results page that performs server-side filtering and paginates results for low-latency delivery

FAQ

How do I invalidate cached pages after updating content?

Invalidate targeted caches in your admin update handlers by deleting cached keys or memoized view functions after database commits.

When should I use server-side rendering vs client-side SPA rendering?

Choose server-side rendering when SEO, initial load speed, or content preview consistency matter; use SPA techniques when rich client interactivity and offline behavior are primary.