server-side-rendering
About
This skill enables server-side rendering using template engines and view layers to generate dynamic HTML on the server. It's ideal for building traditional web applications, implementing MVC architectures, and creating SEO-friendly pages. Key features include data-driven HTML generation with support for caching, streaming, and performance optimization across multiple frameworks.
Quick Install
Claude Code
Recommended/plugin add https://github.com/aj-geddes/useful-ai-promptsgit clone https://github.com/aj-geddes/useful-ai-prompts.git ~/.claude/skills/server-side-renderingCopy and paste this command in Claude Code to install this skill
Documentation
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
# 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
<!-- 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>© {{ 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
// 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
<!-- 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
# 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
# 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
<!-- 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
@app.route('/hello/<name>')
def hello(name):
return render_template('hello.html', name=name)
# hello.html
<h1>Hello, {{ name | capitalize }}!</h1>
GitHub Repository
Related Skills
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
creating-opencode-plugins
MetaThis skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.
polymarket
MetaThis skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.
cloudflare-turnstile
MetaThis skill provides comprehensive guidance for implementing Cloudflare Turnstile as a CAPTCHA-alternative bot protection system. It covers integration for forms, login pages, API endpoints, and frameworks like React/Next.js/Hono, while handling invisible challenges that maintain user experience. Use it when migrating from reCAPTCHA, debugging error codes, or implementing token validation and E2E tests.
