Best Boilerplates for Social Apps 2026
TL;DR
Building a social app means solving the social graph, real-time feeds, and notifications — three fundamentally different problems. No single boilerplate handles all three well. The 2026 stack: Supabase (database + auth + realtime) for the core, Stream (feed infrastructure) or custom fan-out for production-scale feeds, and Pusher/Ably for real-time notifications. For MVP: Supabase realtime subscriptions handle everything. For scale: Stream Feed API.
Key Takeaways
- Social graph: Postgres self-join (
followstable) is the right approach for most apps - Feed: Fan-out on write (push to followers) vs fan-out on read (pull at load time)
- Stream Feed API: Managed feed infrastructure for 10M+ activity items
- Supabase Realtime: Good for MVP, channel-based subscriptions
- Notifications: Supabase row-level subscriptions or Pusher for client push
- Media uploads: Supabase Storage or Cloudflare R2
Why Social Apps Are Hard to Boilerplate
Social apps look deceptively simple — profiles, posts, follows — but the complexity hides in three places where most starters fall short.
First, the social graph itself. A follows table seems trivial until you need to query "posts from people I follow, sorted by time, paginated, with like state" — a join that becomes expensive as your user base grows. You need indexes on both sides of the relationship, and the query shape changes significantly at scale.
Second, feed architecture. The naïve approach (fan-out on read: query posts from followed users at request time) works for MVPs but degrades catastrophically at 100K+ users. Fan-out on write (pre-computing feeds on post creation) is better for reads but adds write complexity and storage cost. Most boilerplates use fan-out on read with caching, which gets you to roughly 50K users before it becomes a problem.
Third, real-time delivery. Supabase Realtime works well for development, but broadcasting to thousands of connected users simultaneously is a different problem than broadcasting to one user. Pusher, Ably, and Socket.io each solve this differently.
Understanding these three problems upfront saves you from picking a boilerplate that works at 100 users but breaks at 10,000.
Top Starters for Social App Projects
1. Supabase Social Starter (Community)
The closest thing to an official social app starter. Built on Supabase's own stack with Postgres, Auth, Realtime, and Storage all integrated. Ships with a working social graph, follow/unfollow, post creation, and basic notifications.
Best for: MVPs and apps targeting under 50K monthly active users. If you're validating a social product idea, this is the fastest path to a working demo.
What it includes:
- User profiles with avatar upload (Supabase Storage)
- Follow/unfollow with RLS policies
- Post feed with fan-out on read
- Supabase Realtime subscriptions for new posts
- Basic notification system (row-level triggers)
- React Query for client-side cache management
Gaps to fill: No algorithmic feed ranking, no media-heavy post types, no direct messaging.
2. T3 Stack + Social Primitives
The T3 Stack (Next.js + TypeScript + Prisma + tRPC + Tailwind) is the most popular choice for teams that want to own every byte of their social app. It's not opinionated about the social layer, which is both its strength and weakness — you build what you need.
Best for: Teams with strong TypeScript/Next.js skills who want full control over data models and business logic. Particularly good when your social features are secondary to another core product function.
Why teams choose it:
- tRPC gives end-to-end type safety for social API calls
- Prisma makes the social graph schema manageable
- The ecosystem is huge — every problem you hit has a community solution
- Easy to add authentication via NextAuth.js or Clerk
What you're building yourself: Feed pagination, notification delivery, real-time subscriptions, media handling. Plan for 2–4 weeks to get a basic social layer production-ready.
3. Makerkit (with Real-Time Extension)
Makerkit's base SaaS boilerplate includes Supabase, and with the real-time extension, you get a usable foundation for social features. Its multi-tenant architecture works well if your social app has "spaces" or "communities" as the organizing unit (like a Slack or Discord clone).
Best for: Community products, group collaboration tools, or platforms where the social layer exists within an organizational context.
Key advantage: Makerkit's billing and subscription management is production-ready out of the box, which matters for monetized community platforms.
4. Supastarter Social
Supastarter's team-based architecture is a natural fit for community-centric social apps. The subdomain-per-tenant model works well for products like "community hubs" where each community gets its own branded experience.
Best for: Multi-community platforms, white-label social products, or networks where isolation between groups is important.
Notable feature: Supastarter includes email notification infrastructure that's easy to wire into social events (new follower, new comment, etc.).
5. Custom: Next.js + Supabase + Stream Feed
For apps where the social feed is the core product (not a secondary feature), assembling a custom stack with Stream Feed is the right call. Stream's infrastructure handles the hard parts of feed architecture at scale.
Best for: Apps where feed quality and performance are competitive differentiators — news aggregators, social discovery products, creator platforms.
Monthly cost: Stream Feed starts at $99/month for 3M activity items. Worth it once you're past validation.
Feature Comparison
| Feature | Supabase Social Starter | T3 Stack | Makerkit | Supastarter | Custom + Stream |
|---|---|---|---|---|---|
| Social graph | ✅ Built-in | Build it | Build it | Build it | Build it |
| Real-time feed | ✅ Supabase | Add Pusher | Add extension | ✅ Supabase | ✅ Stream |
| Notifications | Basic | Build it | Build it | ||
| Media upload | ✅ Storage | Add UploadThing | ✅ Storage | ✅ Storage | Cloudflare R2 |
| Multi-tenancy | ❌ | Add Clerk Orgs | ✅ | ✅ | Build it |
| Feed at scale | ❌ | ❌ | ❌ | ❌ | ✅ Stream |
| Auth included | ✅ | NextAuth | ✅ | ✅ | Add Clerk |
| Price | Free/OSS | Free/OSS | $299 | $299 | Varies |
The Social App Data Model
-- Minimal social graph schema:
CREATE TABLE profiles (
id UUID REFERENCES auth.users(id) PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
display_name TEXT,
bio TEXT,
avatar_url TEXT,
followers_count INTEGER DEFAULT 0,
following_count INTEGER DEFAULT 0,
posts_count INTEGER DEFAULT 0
);
CREATE TABLE follows (
follower_id UUID REFERENCES profiles(id),
following_id UUID REFERENCES profiles(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (follower_id, following_id)
);
CREATE TABLE posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) NOT NULL,
content TEXT NOT NULL,
media_urls TEXT[],
likes_count INTEGER DEFAULT 0,
replies_count INTEGER DEFAULT 0,
reposts_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE likes (
user_id UUID REFERENCES profiles(id),
post_id UUID REFERENCES posts(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, post_id)
);
CREATE TABLE notifications (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) NOT NULL,
actor_id UUID REFERENCES profiles(id) NOT NULL,
type TEXT NOT NULL, -- 'like', 'follow', 'reply', 'mention'
post_id UUID REFERENCES posts(id),
read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS policies:
ALTER TABLE follows ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can see all follows" ON follows FOR SELECT USING (true);
CREATE POLICY "Users can follow others" ON follows FOR INSERT WITH CHECK (auth.uid() = follower_id);
CREATE POLICY "Users can unfollow" ON follows FOR DELETE USING (auth.uid() = follower_id);
Follow/Unfollow with Supabase
// hooks/useSocial.ts:
import { useSupabaseClient } from '@supabase/auth-helpers-react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useFollow(targetUserId: string) {
const supabase = useSupabaseClient();
const queryClient = useQueryClient();
const follow = useMutation({
mutationFn: async () => {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('Not authenticated');
// Insert follow relationship:
await supabase.from('follows').insert({
follower_id: user.id,
following_id: targetUserId,
});
// Create notification for the target user:
await supabase.from('notifications').insert({
user_id: targetUserId,
actor_id: user.id,
type: 'follow',
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profile', targetUserId] });
queryClient.invalidateQueries({ queryKey: ['is-following', targetUserId] });
},
});
const unfollow = useMutation({
mutationFn: async () => {
const { data: { user } } = await supabase.auth.getUser();
await supabase.from('follows')
.delete()
.eq('follower_id', user!.id)
.eq('following_id', targetUserId);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profile', targetUserId] });
},
});
return { follow, unfollow };
}
Activity Feed
// Simple fan-out on read (good for MVPs):
// Fetch posts from people the current user follows
export async function getFeed(userId: string, page = 0) {
const { data } = await supabase
.from('posts')
.select(`
*,
profiles (username, display_name, avatar_url),
likes (user_id)
`)
.in('user_id',
// Subquery: get all users this person follows
supabase.from('follows')
.select('following_id')
.eq('follower_id', userId)
)
.order('created_at', { ascending: false })
.range(page * 20, (page + 1) * 20 - 1);
return data;
}
// Or with a SQL function for performance:
// CREATE FUNCTION get_feed(p_user_id UUID, p_limit INT, p_offset INT)
// RETURNS SETOF posts AS $$
// SELECT p.* FROM posts p
// JOIN follows f ON p.user_id = f.following_id
// WHERE f.follower_id = p_user_id
// ORDER BY p.created_at DESC
// LIMIT p_limit OFFSET p_offset;
// $$ LANGUAGE sql;
Real-Time Feed with Supabase
// components/Feed.tsx — real-time new posts:
'use client';
import { useEffect, useState } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
export function Feed({ userId }: { userId: string }) {
const [newPosts, setNewPosts] = useState<Post[]>([]);
const supabase = createClientComponentClient();
useEffect(() => {
// Subscribe to new posts from followed users:
const channel = supabase
.channel('feed-updates')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts',
},
(payload) => {
// Check if this is from someone we follow:
checkIfFollowing(payload.new.user_id).then((isFollowing) => {
if (isFollowing) {
setNewPosts((prev) => [payload.new as Post, ...prev]);
}
});
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [userId]);
return (
<div>
{newPosts.length > 0 && (
<button className="w-full bg-blue-50 text-blue-600 py-2 rounded-lg mb-4"
onClick={() => setNewPosts([])}>
{newPosts.length} new post{newPosts.length > 1 ? 's' : ''}
</button>
)}
<PostList userId={userId} prependPosts={newPosts} />
</div>
);
}
Stream Feed: Production Scale
npm install getstream
// For high-volume social apps (Twitter-scale):
import { connect } from 'getstream';
const client = connect(
process.env.STREAM_API_KEY!,
process.env.STREAM_API_SECRET!,
process.env.STREAM_APP_ID!
);
// Add a post to the user's feed:
async function addPost(userId: string, post: { content: string; mediaUrls: string[] }) {
const userFeed = client.feed('user', userId);
await userFeed.addActivity({
actor: `SU:${userId}`,
verb: 'post',
object: post.id,
content: post.content,
media_urls: post.mediaUrls,
time: new Date().toISOString(),
});
}
// Get timeline (aggregated feed of followed users):
async function getTimeline(userId: string, limit = 20, offset = 0) {
const timeline = client.feed('timeline', userId);
const { results } = await timeline.get({ limit, offset });
return results;
}
// Follow/unfollow:
async function followUser(follower: string, target: string) {
const followerTimeline = client.feed('timeline', follower);
await followerTimeline.follow('user', target);
}
Notification Architecture: Three Patterns
How you deliver notifications matters for user retention. The three patterns in 2026 are:
1. Supabase row-level triggers (simplest): A database trigger inserts a notification row, and the client subscribes via Supabase Realtime. Works well for low-volume apps. The weakness is that client must be connected — background push notifications require additional infrastructure.
2. Pusher/Ably channel (mid-tier): Server emits events to Pusher on notification creation. Clients subscribe to personal channels. Better delivery guarantees than Supabase Realtime for mobile-web hybrid apps. Cost: Pusher starts at $49/month for 500K monthly messages.
3. Push notifications + in-app (full stack): Web Push API for browser notifications + a notification inbox in-app. Requires a service worker and notification permission UI. Most social apps that care about re-engagement need this eventually.
If you're building an MVP, start with Supabase Realtime. Add Pusher or web push when DAU-driven engagement becomes a product priority.
Moderation: The Feature You Can't Skip
Every social app needs content moderation. Building a social platform without moderation infrastructure is choosing to deal with spam, harassment, and illegal content reactively instead of proactively — which is far more damaging when it happens.
The minimum viable moderation system has four components:
User reporting. Every piece of user-generated content needs a report button. Store reports in a database table with content type, content ID, reporter ID, reason, and status. A human reviewer (you, early on) reviews the queue.
Block/mute at the user level. Users should be able to block other users — blocking means neither party sees the other's content and they cannot interact. Mute is softer — the muting user doesn't see the muted user's content, but the muted user can still see theirs. Both are essential for reducing harassment.
Content removal with appeal. When you remove content or suspend an account, notify the user with the reason. Provide an appeal mechanism — even a simple email to support. Platforms without appeals create PR problems when they get false positives.
Automated spam detection. At minimum, rate limit post creation (no more than X posts per hour), detect duplicate content, and flag accounts that follow hundreds of users in a short period. These catch the most obvious spam without complex ML.
Integrating a managed moderation service (Perspective API for toxicity scoring, Hive Moderation for image classification) is worth the cost once you have meaningful traffic — manual review doesn't scale.
Viral Growth Mechanics
Social apps live by their network effects, but network effects don't spontaneously emerge — they need activation. The product design choices that help new social apps get to critical mass:
Import contacts. Allowing users to find their existing connections from email, phone contacts, or other social networks dramatically reduces the cold-start problem. New users with existing connections engage immediately; new users with no connections churn.
Cross-posting and sharing. Make it easy to share individual posts to Twitter, LinkedIn, or other networks. Each share is a free distribution event that brings new users to your platform.
Email notifications. The most reliable re-engagement mechanism is transactional email: "Someone liked your post", "You have a new follower", "You were mentioned". These bring dormant users back. Keep them tasteful — one notification email per day maximum, never more.
Activity digests for inactive users. If a user hasn't logged in for 7 days, send a "Here's what you missed" digest featuring content from people they follow. This outperforms generic "We miss you" emails significantly.
Performance Gotchas
Counter denormalization vs. live counts: The schema above uses followers_count, following_count, and posts_count as denormalized columns. They're faster to read but require careful update logic. Alternatively, use SELECT COUNT(*) — fine for low traffic, expensive at scale. Most boilerplates use denormalized counters with Postgres triggers to maintain them.
Missing indexes: The follows table needs an index on following_id for the reverse lookup ("who follows me"). Without it, the query WHERE following_id = $1 becomes a full table scan.
N+1 queries in feed: When rendering a feed of 20 posts, a naïve implementation might fire 20 separate queries to check if the current user liked each post. Batch this into one WHERE post_id IN (...) query.
Choosing Your Path
Use Supabase Realtime for social if:
- MVP / early stage (under 10K users)
- Budget constrained
- Team knows SQL well
- Simple feed (chronological, not algorithmic)
Use Stream Feed if:
- Need to scale to millions of activities
- Need aggregated feeds (notifications digest)
- Want fan-out handled automatically
- Enterprise features (analytics, moderation)
Use Pusher/Ably for notifications if:
- Need client-push (user gets ping, no polling)
- Native mobile app notifications
- Real-time typing indicators
- Supabase realtime isn't granular enough
Avoid building from scratch if:
- You're implementing feed fan-out yourself
- Writing your own social graph traversal SQL
- Building your own notification delivery system
- Focus on your product, not infrastructure
Related Resources
For real-time collaboration features beyond social feeds, see best boilerplates for real-time collaboration apps. If your social app includes community spaces or groups, best boilerplates for community apps covers multi-tenant patterns. For the notification infrastructure specifically, the how to add in-app notifications to your SaaS boilerplate guide covers the full implementation.
Methodology
This guide is based on reviewing the GitHub repositories, documentation, and community feedback for each boilerplate as of Q1 2026. Feed architecture benchmarks reference Supabase's published benchmarks and Stream's public documentation. Pricing is current as of April 2026. The social app landscape has evolved significantly with the federated protocol ecosystem (ActivityPub, AT Protocol) gaining traction — teams building social apps in 2026 should evaluate whether open federation support is a product requirement before committing to a platform-native architecture that would require significant rework to add later.
Find social app boilerplates at StarterPick.