<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex</title>
    <description>The latest articles on DEV Community by Alex (@alexrosrgz).</description>
    <link>https://dev.to/alexrosrgz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3647217%2F40d47e49-76ab-43b9-b9b8-8f35c736966d.jpg</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/alexrosrgz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexrosrgz"/>
    <language>en</language>
    <item>
      <title>Building Synapse: A Full-Featured Social Media App with React Native &amp; Supabase (Launching Soon!)</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 23 Dec 2025 21:41:53 +0000</pubDate>
      <link>https://dev.to/alexrosrgz/building-synapse-a-full-featured-social-media-app-with-react-native-supabase-launching-soon-1f4f</link>
      <guid>https://dev.to/alexrosrgz/building-synapse-a-full-featured-social-media-app-with-react-native-supabase-launching-soon-1f4f</guid>
      <description>&lt;h2&gt;
  
  
  What is Synapse?
&lt;/h2&gt;

&lt;p&gt;Synapse is a social media platform designed for meaningful connections. Think X/Threads, but with support for rich content blocks, mathematical equations, audio posts, files, and a privacy-first approach and much more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-block posts (text, images, videos, audio, files, live photos)&lt;/li&gt;
&lt;li&gt;Markdown with syntax highlighting and MathJax support&lt;/li&gt;
&lt;li&gt;Feed recommendations&lt;/li&gt;
&lt;li&gt;Real-time direct messaging&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why We Built This
&lt;/h2&gt;

&lt;p&gt;The idea for Synapse came from a frustration that many researchers and developers share: &lt;strong&gt;existing social platforms aren't built for technical discourse.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problems We Kept Running Into
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Fragmented Discussions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try following a machine learning research discussion on X (Twitter). A single paper breakdown gets split into 15+ tweets in a thread. Equations are screenshots. Code is images. Context gets lost in quote tweets and replies. It's chaos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e5mtbunf6w1l316kkbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e5mtbunf6w1l316kkbj.png" alt="Three apps are needed to read the whole content" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No Built-in Support for Rich Content&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Want to share a PDF of your paper? Upload an audio explanation of your code? Attach a dataset file? You can't. Current platforms treat everything as text + images/video, forcing researchers and developers to link out to external services for anything beyond basic media.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61uru26rpkjr04r6lark.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61uru26rpkjr04r6lark.png" alt="The way research is shared on X" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Discovery is Broken&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding the right people to follow in your niche is surprisingly hard. Algorithms optimize for engagement, not relevance. There's no good way to discover researchers in your specific subfield or developers working on similar problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vision
&lt;/h3&gt;

&lt;p&gt;We wanted a platform where you could write a single, cohesive post that includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your explanation in markdown with proper formatting&lt;/li&gt;
&lt;li&gt;Mathematical equations rendered beautifully (LaTeX/MathJax)&lt;/li&gt;
&lt;li&gt;Code snippets with syntax highlighting&lt;/li&gt;
&lt;li&gt;The actual PDF or dataset file&lt;/li&gt;
&lt;li&gt;An audio walkthrough for those who prefer listening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmb03h2qi2cp3ma4c1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmb03h2qi2cp3ma4c1q.png" alt="A post on Synapse - all in one" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All in one place. No threads. No external links. No screenshots of equations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;p&gt;We're building Synapse primarily for two communities:&lt;/p&gt;

&lt;h3&gt;
  
  
  Researchers &amp;amp; Academics
&lt;/h3&gt;

&lt;p&gt;Scientists who want to share their work, discuss papers, and connect with peers in their field. Whether you're in ML, physics, biology, or any technical discipline, you deserve a platform that speaks your language - literally, with native LaTeX support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developers &amp;amp; Technical Creators
&lt;/h3&gt;

&lt;p&gt;Programmers who want to share ideas, tutorials, and projects in a beautiful, readable format. Write technical content with proper code highlighting, attach relevant files, and build an audience that cares about substance over virality.&lt;/p&gt;

&lt;p&gt;We believe the best ideas deserve better than a character limit and a thread of fragmented thoughts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Native 0.81&lt;/strong&gt; with the new architecture enabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK 54&lt;/strong&gt; for rapid development and OTA updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React 19&lt;/strong&gt; with the latest concurrent features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; in strict mode (no &lt;code&gt;any&lt;/code&gt; allowed!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;We went with a hybrid approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Query 5&lt;/strong&gt; for server state and caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zustand&lt;/strong&gt; for client-side stores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context API&lt;/strong&gt; for global app state (Auth, Audio, Analytics)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; as our Backend-as-a-Service

&lt;ul&gt;
&lt;li&gt;PostgreSQL for the database&lt;/li&gt;
&lt;li&gt;Supabase Auth with email OTP&lt;/li&gt;
&lt;li&gt;Supabase Storage (S3-compatible) for media&lt;/li&gt;
&lt;li&gt;Realtime subscriptions for live updates&lt;/li&gt;
&lt;li&gt;Edge Functions (Deno) for serverless compute&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Deep Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Content Block System
&lt;/h3&gt;

&lt;p&gt;Instead of storing posts as flat text, we designed a flexible block-based architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ContentBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TextBlock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MediaBlock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AudioBlock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FileBlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MediaBlock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MediaItem&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MediaItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;live_photo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;storagePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Live photos have separate video/photo URLs&lt;/span&gt;
  &lt;span class="nl"&gt;videoStoragePath&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posts can contain mixed content types&lt;/li&gt;
&lt;li&gt;Each block handles its own rendering logic&lt;/li&gt;
&lt;li&gt;Easy to add new block types in the future&lt;/li&gt;
&lt;li&gt;Type-safe with runtime type guards&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Optimistic Messaging
&lt;/h3&gt;

&lt;p&gt;Nobody wants to wait for a server response to see their message. We implemented optimistic updates with local IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate local ID for immediate display&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`local_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Show message immediately with "pending" status&lt;/span&gt;
&lt;span class="nf"&gt;addMessageToCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Server confirms and provides real ID&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conversation_id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Reconcile local and server states&lt;/span&gt;
&lt;span class="nf"&gt;replaceLocalMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part? Handling edge cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if the server response arrives before the optimistic update?&lt;/li&gt;
&lt;li&gt;What if the user sends multiple messages quickly?&lt;/li&gt;
&lt;li&gt;How do we handle failures gracefully?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built an &lt;code&gt;OptimisticMessageManager&lt;/code&gt; that handles all these scenarios with proper reconciliation logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Signed URLs with Smart Caching
&lt;/h3&gt;

&lt;p&gt;Media files in Supabase Storage require signed URLs for private access. The challenge: these URLs expire!&lt;/p&gt;

&lt;p&gt;Our solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache signed URLs with TTL tracking&lt;/li&gt;
&lt;li&gt;Refresh URLs 5 minutes before expiration&lt;/li&gt;
&lt;li&gt;Batch URL requests to reduce API calls&lt;/li&gt;
&lt;li&gt;Propagate &lt;code&gt;postId&lt;/code&gt; context for cache invalidation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SignedUrlService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CachedUrl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storagePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storagePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isExpiringSoon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchAndCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storagePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Feed Recommendation Engine
&lt;/h3&gt;

&lt;p&gt;We built a server-side recommendation RPC that scores posts using multiple factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recency&lt;/li&gt;
&lt;li&gt;Engagement metrics&lt;/li&gt;
&lt;li&gt;User's social graph&lt;/li&gt;
&lt;li&gt;Content type preferences
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Edge Function: feed-recommendation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;excludeIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateRelevanceScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userPreferences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;scored&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users can toggle between chronological and recommended feeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Audio Recording with Gesture Detection
&lt;/h3&gt;

&lt;p&gt;We built custom &lt;code&gt;HoldToRecordButton&lt;/code&gt; and &lt;code&gt;HoldToRecordOverlay&lt;/code&gt; components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long press starts recording&lt;/li&gt;
&lt;li&gt;Drag to cancel zone aborts&lt;/li&gt;
&lt;li&gt;Visual waveform feedback&lt;/li&gt;
&lt;li&gt;Haptic feedback on state changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This required deep integration with React Native Gesture Handler and Expo Audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start with TypeScript Strict Mode
&lt;/h3&gt;

&lt;p&gt;We enabled strict mode from day one. It caught countless bugs during development that would have been runtime errors otherwise. The upfront investment pays dividends.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Server-Side Logic &amp;gt; Client-Side Complexity
&lt;/h3&gt;

&lt;p&gt;Instead of managing complex state on the client (like cascade deletes when blocking a user), we use database triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;on_user_blocked&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;blocked_users&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;cleanup_follows_on_block&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures data consistency even if the client crashes mid-operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Design for Offline First
&lt;/h3&gt;

&lt;p&gt;We use MMKV (a fast key-value store) for local caching with TTLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profiles: 5 days&lt;/li&gt;
&lt;li&gt;Feed data: 2 days&lt;/li&gt;
&lt;li&gt;Messages: Synced with pending states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app feels snappy because most data is served from cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Don't Underestimate Media Handling
&lt;/h3&gt;

&lt;p&gt;Image/video upload seems simple until you consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compression for faster uploads&lt;/li&gt;
&lt;li&gt;Thumbnail generation&lt;/li&gt;
&lt;li&gt;Progress tracking&lt;/li&gt;
&lt;li&gt;Upload cancellation&lt;/li&gt;
&lt;li&gt;Quota enforcement&lt;/li&gt;
&lt;li&gt;Signed URL management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our &lt;code&gt;postService.ts&lt;/code&gt; is 56KB for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. React Query is a Game Changer
&lt;/h3&gt;

&lt;p&gt;Before React Query, we had manual cache invalidation everywhere. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUserPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Mutations automatically invalidate related queries&lt;/span&gt;
&lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We're polishing the final details before App Store submission:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance profiling on older devices&lt;/li&gt;
&lt;li&gt;Accessibility audit&lt;/li&gt;
&lt;li&gt;Final round of beta testing&lt;/li&gt;
&lt;li&gt;App Store assets and screenshots&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Want to Follow Along?
&lt;/h2&gt;

&lt;p&gt;We're building in public! Follow our journey:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X: &lt;a href="https://x.com/alexrosrgz" rel="noopener noreferrer"&gt;https://x.com/alexrosrgz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/alexrosrgz/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/alexrosrgz/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Landing page and waitlist: &lt;a href="https://synapsehub.social" rel="noopener noreferrer"&gt;synapsehub.social&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building something similar or have questions about our approach, drop a comment below. Happy to share more details about any aspect of the stack!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What technical challenges have you faced building mobile apps? Let's discuss in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>socialmedia</category>
      <category>startup</category>
      <category>reactnative</category>
    </item>
  </channel>
</rss>
