RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-03-31Advanced

Rork Max Social App Features Guide — Timeline, Follow System & Real-Time Feed

A production-grade walkthrough for building follow systems, timeline feeds, optimistic likes/comments, real-time sync, and push notifications in Rork Max with Supabase — distilled from years of running quiet indie utility apps in production.

rork-max40social-apptimelinefollowrealtime2supabase3feed

Premium Article

I build indie iOS and Android apps — a small portfolio of wallpaper, calming-tone, and intention-style utilities. For most of their life these apps were intentionally quiet: single-purpose tools that did one thing well. But over the last couple of years, the same users started asking the same things: "Can I see what other people posted?" "Can I share my favorite with a friend?"

Up to a few thousand DAU, social features are a pleasant add-on. Past that — once retention starts to be measured at 7 and 30 days — the shape of your database, the order of your timeline, and the way you fire notifications stop being technical details and start being the things that decide whether people come back. I once shipped an "off-graph popular posts mixed into the home feed" experiment on one of my wallpaper apps and dropped Day 7 retention from 41% to 28%. It took six months to get it back, and the lessons from that recovery are baked into this guide.

This is a hands-on guide to building a social layer in Rork Max with Supabase that doesn't break under load. We'll cover the follow graph, timeline feeds, optimistic likes and comments, real-time updates, and push notifications end-to-end. By the time you finish, you should know exactly which piece of your own app to touch first.

Setup and context

Social features boil down to two questions: how do you preserve the connection between users, and what do you show in the timeline? Twitter took off because of the follow model and Instagram pulled ahead of Facebook because of a clean follow-only photo feed. Both made the deliberate choice to keep the home timeline pure.

What's harder to copy is the discipline. Most "social app v1" implementations end up technically working but visually noisy — every feature on, every notification firing, every user a little bit unhappy. The shortcut I keep coming back to is to decide what not to build at each layer (database, timeline ordering, notifications) before you decide what to build. That decision is what makes the result feel kind to your users.

The chapters below walk through database design, follow graph, timeline feed, optimistic likes/comments, real-time sync, infinite scroll, and push notifications, in that order, with Supabase as the backend.


Database Design — The Foundation of Everything

Your schema design determines whether your social app scales to millions of users or breaks at thousands. Let's build it right from the start.

Table Structure

profiles
├── id (UUID, PK)
├── user_id (UUID, FK → auth.users)
├── username (TEXT, UNIQUE)
├── display_name (TEXT)
├── avatar_url (TEXT)
├── bio (TEXT)
├── followers_count (INT)
├── following_count (INT)
├── created_at (TIMESTAMP)
└── updated_at (TIMESTAMP)

follows
├── id (UUID, PK)
├── follower_id (UUID, FK → profiles)
├── following_id (UUID, FK → profiles)
├── created_at (TIMESTAMP)
└── UNIQUE(follower_id, following_id)

posts
├── id (UUID, PK)
├── author_id (UUID, FK → profiles)
├── content (TEXT)
├── image_urls (TEXT[])
├── likes_count (INT)
├── comments_count (INT)
├── created_at (TIMESTAMP)
├── updated_at (TIMESTAMP)
└── deleted_at (TIMESTAMP, soft delete)

likes
├── id (UUID, PK)
├── post_id (UUID, FK → posts)
├── user_id (UUID, FK → profiles)
├── created_at (TIMESTAMP)
└── UNIQUE(post_id, user_id)

comments
├── id (UUID, PK)
├── post_id (UUID, FK → posts)
├── author_id (UUID, FK → profiles)
├── parent_comment_id (UUID, FK → comments, nullable)
├── content (TEXT)
├── likes_count (INT)
├── created_at (TIMESTAMP)
├── updated_at (TIMESTAMP)
└── deleted_at (TIMESTAMP)

notifications
├── id (UUID, PK)
├── user_id (UUID, FK → profiles)
├── type (TEXT: 'like', 'comment', 'follow')
├── triggered_by_id (UUID, FK → profiles)
├── post_id (UUID, FK → posts, nullable)
├── comment_id (UUID, FK → comments, nullable)
├── is_read (BOOLEAN)
├── created_at (TIMESTAMP)
└── INDEX(user_id, is_read)

Supabase Implementation

Run this in the Supabase SQL Editor:

-- profiles table
CREATE TABLE profiles (
  id UUID PRIMARY KEY DEFAULT auth.uid(),
  user_id UUID REFERENCES auth.users NOT NULL,
  username TEXT UNIQUE NOT NULL,
  display_name TEXT,
  avatar_url TEXT,
  bio TEXT,
  followers_count INT DEFAULT 0,
  following_count INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT now(),
  updated_at TIMESTAMP DEFAULT now()
);
 
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public profiles are viewable by all"
  ON profiles FOR SELECT USING (true);
CREATE POLICY "Users can update their own profile"
  ON profiles FOR UPDATE USING (auth.uid() = id);
 
-- follows table
CREATE TABLE follows (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  follower_id UUID REFERENCES profiles NOT NULL,
  following_id UUID REFERENCES profiles NOT NULL,
  created_at TIMESTAMP DEFAULT now(),
  UNIQUE(follower_id, following_id),
  CHECK (follower_id != following_id)
);
 
ALTER TABLE follows ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Follows are public" ON follows FOR SELECT USING (true);
CREATE POLICY "Users can manage their follows" ON follows
  FOR INSERT WITH CHECK (auth.uid() = follower_id);
CREATE POLICY "Users can unfollow" ON follows
  FOR DELETE USING (auth.uid() = follower_id);
 
-- posts table
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  author_id UUID REFERENCES profiles NOT NULL,
  content TEXT NOT NULL,
  image_urls TEXT[],
  likes_count INT DEFAULT 0,
  comments_count INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT now(),
  updated_at TIMESTAMP DEFAULT now(),
  deleted_at TIMESTAMP
);
 
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public posts are viewable" ON posts
  FOR SELECT USING (deleted_at IS NULL);
CREATE POLICY "Users can create posts" ON posts
  FOR INSERT WITH CHECK (auth.uid() = author_id);
CREATE POLICY "Users can update own posts" ON posts
  FOR UPDATE USING (auth.uid() = author_id);
 
-- likes table
CREATE TABLE likes (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES posts NOT NULL,
  user_id UUID REFERENCES profiles NOT NULL,
  created_at TIMESTAMP DEFAULT now(),
  UNIQUE(post_id, user_id)
);
 
ALTER TABLE likes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Likes are public" ON likes FOR SELECT USING (true);
CREATE POLICY "Users can like" ON likes
  FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can unlike" ON likes
  FOR DELETE USING (auth.uid() = user_id);
 
-- comments table
CREATE TABLE comments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES posts NOT NULL,
  author_id UUID REFERENCES profiles NOT NULL,
  parent_comment_id UUID REFERENCES comments,
  content TEXT NOT NULL,
  likes_count INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT now(),
  updated_at TIMESTAMP DEFAULT now(),
  deleted_at TIMESTAMP
);
 
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Comments are public" ON comments
  FOR SELECT USING (deleted_at IS NULL);
CREATE POLICY "Users can comment" ON comments
  FOR INSERT WITH CHECK (auth.uid() = author_id);
CREATE POLICY "Users can update own comments" ON comments
  FOR UPDATE USING (auth.uid() = author_id);
 
-- notifications table
CREATE TABLE notifications (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES profiles NOT NULL,
  type TEXT CHECK (type IN ('like', 'comment', 'follow')),
  triggered_by_id UUID REFERENCES profiles NOT NULL,
  post_id UUID REFERENCES posts,
  comment_id UUID REFERENCES comments,
  is_read BOOLEAN DEFAULT false,
  created_at TIMESTAMP DEFAULT now()
);
 
ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own notifications" ON notifications
  FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can mark as read" ON notifications
  FOR UPDATE USING (auth.uid() = user_id);
 
-- Create indexes for performance
CREATE INDEX idx_profiles_username ON profiles(username);
CREATE INDEX idx_follows_follower_id ON follows(follower_id);
CREATE INDEX idx_follows_following_id ON follows(following_id);
CREATE INDEX idx_posts_author_id ON posts(author_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
CREATE INDEX idx_likes_post_id ON likes(post_id);
CREATE INDEX idx_likes_user_id ON likes(user_id);
CREATE INDEX idx_comments_post_id ON comments(post_id);
CREATE INDEX idx_comments_author_id ON comments(author_id);
CREATE INDEX idx_notifications_user_id ON notifications(user_id, is_read);

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Design judgments that kept social features from breaking at scale (no off-graph mixing, when not to denormalize follower_count, why LWW is enough for likes) — drawn from real production operation
A staged-fallback strategy for Supabase Realtime before you hit the ~10k concurrent-connection wall, with retry and invalid-notification suppression code
Push-notification segmentation thresholds that actually move OPEN rate, plus a runbook for recovering from a misfire
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-13
Your Detox and Maestro E2E Suite Was All Green — but the Retries Were Hiding a Flaky Test That Crashed in Production
Your E2E suite is green, yet production still crashes. The culprit: auto-retries quietly swallowing flaky failures. Field notes on measuring per-test flake rate, quarantining, and keeping only real failures as release gates.
Dev Tools2026-07-01
Rork Max Cloud Compilation — Shipping Native Apps Without Owning a Mac
How Rork Max's cloud compilation lets you build, device-test, and publish native iOS/iPadOS apps without a local Mac — including how to triage failed builds and when you should still keep a real Mac around.
Dev Tools2026-06-17
Checking Age Without Collecting Birthdays — Wiring the Declared Age Range API into a Rork App
How to use the iOS 26 Declared Age Range API to receive an age band without ever storing a birthdate, with both the Rork Max native Swift path and the standard Rork (Expo) native-module bridge, plus where to draw the responsibility boundary.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →