<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Season Saw on Medium]]></title>
        <description><![CDATA[Stories by Season Saw on Medium]]></description>
        <link>https://medium.com/@season.saw?source=rss-2cb7ca6a7cd7------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*4h-T20-vv602g9Nc.jpg</url>
            <title>Stories by Season Saw on Medium</title>
            <link>https://medium.com/@season.saw?source=rss-2cb7ca6a7cd7------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 18 Oct 2026 23:24:58 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@season.saw/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Building Cross-Platform Apps with the ENSS Stack: Expo, NestJS, and Supabase]]></title>
            <link>https://medium.com/@season.saw/building-cross-platform-apps-with-the-enss-stack-expo-nestjs-and-supabase-4d688fe38d9c?source=rss-2cb7ca6a7cd7------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d688fe38d9c</guid>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[supabase]]></category>
            <dc:creator><![CDATA[Season Saw]]></dc:creator>
            <pubDate>Mon, 31 Mar 2025 09:26:36 GMT</pubDate>
            <atom:updated>2025-03-31T09:26:36.038Z</atom:updated>
            <content:encoded><![CDATA[<p>In today’s fast-paced development world, choosing the right technology stack can make the difference between a successful project and a maintenance nightmare. This post introduces the <strong>ENSS Stack</strong>, a powerful combination of modern technologies designed for building cross-platform applications efficiently:</p><ul><li><strong>E</strong>xpo (React Native) for the mobile frontend</li><li><strong>N</strong>estJS for the backend API</li><li><strong>S</strong>upabase for authentication and database</li><li><strong>S</strong>tack (full-stack integration)</li></ul><p>Let’s explore how these technologies work together to create a seamless development experience while delivering performant applications.</p><h3>The Problem: Cross-Platform Development Challenges</h3><p>Building applications that work across different platforms often involves complex tradeoffs:</p><ol><li>Native app performance vs. development speed</li><li>Backend security and structure vs. flexibility</li><li>Authentication implementation and management</li><li>Database setup, maintenance, and scaling</li></ol><p>The ENSS Stack addresses these challenges by combining best-in-class technologies that work seamlessly together.</p><h3>Why Choose the ENSS Stack?</h3><h3>1. Expo (React Native)</h3><p>Expo simplifies React Native development with:</p><ul><li>Fast iteration through hot reloading</li><li>Simple setup without the need for Xcode or Android Studio</li><li>Managed workflow handling complex native modules</li><li>Over-the-air updates for quick bug fixes</li><li>TypeScript support for better type safety</li></ul><h3>2. NestJS Backend</h3><p>NestJS provides:</p><ul><li>A structured, Angular-inspired architecture</li><li>Built-in TypeScript support</li><li>Dependency injection system for better testing</li><li>Comprehensive documentation and community support</li><li>Modular design for scalable applications</li></ul><h3>3. Supabase</h3><p>Supabase offers:</p><ul><li>PostgreSQL database with real-time capabilities</li><li>Built-in authentication and user management</li><li>Row-level security for fine-grained access control</li><li>Storage solution for files and media</li><li>Open-source infrastructure with cloud and self-hosting options</li></ul><h3>4. Full-Stack Integration</h3><p>The integration between these technologies:</p><ul><li>Type-safety across the entire application</li><li>Consistent development experience</li><li>Shared authentication context</li><li>Simplified deployment and maintenance</li></ul><h3>Getting Started with the ENSS Stack</h3><p>Let’s walk through setting up a basic application using the ENSS stack.</p><h3>Prerequisites</h3><p>Before we begin, ensure you have:</p><ul><li>Node.js 18+ and npm installed</li><li>A Supabase account (free tier available)</li><li>Basic familiarity with TypeScript, React, and API concepts</li></ul><h3>Project Structure</h3><p>The ENSS stack template follows this structure:</p><pre>enss-stack/<br>├── mobile/           # Expo React Native mobile app<br>├── backend/          # NestJS backend API</pre><h3>Step 1: Setting Up Supabase</h3><ol><li>Create a new Supabase project at <a href="https://supabase.com/">supabase.com</a></li><li>Enable Google authentication in the Auth settings</li><li>Create a “profiles” table in the database with the following schema:</li></ol><pre>create table<br>  public.profiles (<br>    id uuid not null references auth.users on delete cascade,<br>    updated_at timestamp with time zone null,<br>    username text null,<br>    full_name text null,<br>    avatar_url text null,<br>    constraint profiles_pkey primary key (id)<br>  ) tablespace pg_default;</pre><p>4. Set up Row Level Security policies for the profiles table:</p><pre>alter table public.profiles enable row level security;<br><br>create policy &quot;Users can view their own profile&quot; on profiles<br>  for select using (auth.uid() = id);<br><br>create policy &quot;Users can update their own profile&quot; on profiles<br>  for update using (auth.uid() = id);</pre><h3>Step 2: Setting Up the NestJS Backend</h3><ol><li>Clone the ENSS stack template:</li></ol><pre>git clone https://github.com/yourusername/enss-stack.git<br>cd enss-stack/backend</pre><p>2. Install dependencies:</p><pre>npm install</pre><p>3. Configure environment variables by copying and updating the .env.example file:</p><pre># Supabase Configuration<br>NEXT_PUBLIC_SUPABASE_URL=&quot;your-supabase-url&quot;<br>NEXT_PUBLIC_SUPABASE_ANON_KEY=&quot;your-supabase-anon-key&quot;<br>SUPABASE_SERVICE_ROLE_KEY=&quot;your-supabase-service-role-key&quot; <br>SUPABASE_CONNECTION_STRING=&quot;your-supabase-connection-string&quot;<br><br># Server Configuration<br>PORT=3000</pre><p>4. Start the development server:</p><pre>npm run start:dev</pre><p>The NestJS backend includes several key components:</p><h4>Supabase Service</h4><p>This service initializes and manages the connection to Supabase:</p><pre>@Injectable()<br>export class SupabaseService implements OnModuleInit {<br>  private adminClient: SupabaseClient;<br><br>  onModuleInit() {<br>    // Initialize the admin client on module initialization<br>    this.adminClient = this.createAdminClient();<br>  }<br><br>  createAdminClient(): SupabaseClient {<br>    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;<br>    const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;<br><br>    if (!supabaseUrl || !supabaseServiceRoleKey) {<br>      throw new Error(<br>        &#39;Missing Supabase URL or service role key. Please check your environment variables.&#39;,<br>      );<br>    }<br><br>    return createClient(supabaseUrl, supabaseServiceRoleKey, {<br>      auth: {<br>        autoRefreshToken: false,<br>        persistSession: false,<br>      },<br>    });<br>  }<br><br>  getAdminClient(): SupabaseClient {<br>    return this.adminClient;<br>  }<br>}</pre><h4>Auth Guard</h4><p>The SupabaseAuthGuard protects routes that require authentication:</p><pre>@Injectable()<br>export class SupabaseAuthGuard implements CanActivate {<br>  constructor(private readonly supabaseService: SupabaseService) {}<br><br>  async canActivate(context: ExecutionContext): Promise&lt;boolean&gt; {<br>    const request = context.switchToHttp().getRequest();<br>    const authHeader = request.headers.authorization;<br><br>    if (!authHeader || !authHeader.startsWith(&#39;Bearer &#39;)) {<br>      throw new UnauthorizedException(&#39;Missing or invalid authorization token&#39;);<br>    }<br><br>    const token = authHeader.split(&#39; &#39;)[1];<br>    const adminClient = this.supabaseService.getAdminClient();<br><br>    try {<br>      // Use Supabase admin client to verify the JWT without RLS restrictions<br>      const { data, error } = await adminClient.auth.getUser(token);<br><br>      if (error || !data.user) {<br>        throw new UnauthorizedException(&#39;Invalid token or user not found&#39;);<br>      }<br><br>      // Attach the user to the request object for later use<br>      request.user = data.user;<br>      return true;<br>    } catch (error) {<br>      throw new UnauthorizedException(&#39;Authentication failed&#39;);<br>    }<br>  }<br>}</pre><h4>User Decorator</h4><p>A custom decorator makes it easy to access the authenticated user:</p><pre>export const User = createParamDecorator(<br>  (data: string | undefined, ctx: ExecutionContext) =&gt; {<br>    const request = ctx.switchToHttp().getRequest();<br>    const user = request.user;<br>    <br>    // If a property key is provided, return that specific property<br>    return data ? user?.[data] : user;<br>  },<br>);</pre><h4>Auth Controller</h4><p>Protected routes can be created with minimal code:</p><pre>@Controller(&#39;auth&#39;)<br>export class AuthController {<br>  constructor(private readonly authService: AuthService) {}<br><br>  @UseGuards(SupabaseAuthGuard)<br>  @Get(&#39;profile&#39;)<br>  async getProfile(@User() user) {<br>    return {<br>      user,<br>      message: &#39;You have successfully accessed a protected route&#39;,<br>    };<br>  }<br><br>  @UseGuards(SupabaseAuthGuard)<br>  @Get(&#39;user-data&#39;)<br>  async getUserData(@User(&#39;id&#39;) userId: string) {<br>    // Get user data from Supabase database<br>    const userData = await this.authService.getUserData(userId);<br>    return { userData };<br>  }<br><br>  @Get(&#39;public&#39;)<br>  getPublicMessage() {<br>    return {<br>      message: &#39;This is a public endpoint that anyone can access&#39;,<br>    };<br>  }<br>}</pre><h3>Step 3: Setting Up the Expo Mobile App</h3><ol><li>Navigate to the mobile app directory:</li></ol><pre>cd ../mobile</pre><ol><li>Install dependencies:</li></ol><pre>npm install</pre><ol><li>Configure environment variables by copying and updating the .env.example file:</li></ol><pre># Supabase Configuration<br>EXPO_PUBLIC_SUPABASE_URL=&quot;your-supabase-url&quot;<br>EXPO_PUBLIC_SUPABASE_ANON_KEY=&quot;your-supabase-anon-key&quot;<br><br># API Configuration<br>EXPO_PUBLIC_API_BASE_URL=&quot;http://localhost:3000&quot;</pre><ol><li>Start the Expo development server:</li></ol><pre>npm start</pre><p>The mobile app includes several key components:</p><h4>Supabase Client</h4><p>The supabase client is configured with AsyncStorage for token persistence:</p><pre>import &quot;react-native-url-polyfill/auto&quot;;<br>import AsyncStorage from &quot;@react-native-async-storage/async-storage&quot;;<br>import { createClient } from &quot;@supabase/supabase-js&quot;;<br>import { Database } from &quot;../types/supabase&quot;;<br><br>const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!;<br>const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!;<br><br>export const supabase = createClient&lt;Database&gt;(supabaseUrl, supabaseAnonKey, {<br>    auth: {<br>        storage: AsyncStorage,<br>        autoRefreshToken: true,<br>        persistSession: true,<br>        detectSessionInUrl: true,<br>    },<br>});</pre><h4>Auth Context</h4><p>A React context manages authentication state across the app:</p><pre>const AuthContext = createContext&lt;AuthContextType | undefined&gt;(undefined);<br><br>export const AuthProvider: React.FC&lt;{ children: React.ReactNode }&gt; = ({ children }) =&gt; {<br>    const [user, setUser] = useState&lt;User | null&gt;(null);<br>    const [session, setSession] = useState&lt;Session | null&gt;(null);<br>    const [isLoading, setIsLoading] = useState(true);<br>    const [error, setError] = useState&lt;AuthError | null&gt;(null);<br><br>    // Authentication state management and methods...<br>    return (<br>        &lt;AuthContext.Provider value={{ <br>            user, <br>            session, <br>            isLoading, <br>            error, <br>            signInWithGoogle, <br>            signOut, <br>            clearError <br>        }}&gt;<br>            {children}<br>        &lt;/AuthContext.Provider&gt;<br>    );<br>};<br><br>export const useAuth = () =&gt; {<br>    const context = useContext(AuthContext);<br>    if (context === undefined) {<br>        throw new Error(&#39;useAuth must be used within an AuthProvider&#39;);<br>    }<br>    return context;<br>};</pre><h4>Login Screen</h4><p>The login screen uses the auth context for Google authentication:</p><pre>export default function LoginScreen() {<br>    const { user, isLoading, signInWithGoogle, error, clearError } = useAuth();<br><br>    // Handle authentication errors<br>    useEffect(() =&gt; {<br>        if (error) {<br>            Alert.alert(<br>                &#39;Authentication Error&#39;,<br>                error.message,<br>                [{ text: &#39;OK&#39;, onPress: clearError }]<br>            );<br>        }<br>    }, [error, clearError]);<br><br>    // If the user is already authenticated, redirect to the main app<br>    if (user &amp;&amp; !isLoading) {<br>        return &lt;Redirect href=&quot;/(tabs)&quot; /&gt;;<br>    }<br><br>    return (<br>        &lt;View style={styles.container}&gt;<br>            &lt;Text style={styles.title}&gt;ENSS Stack&lt;/Text&gt;<br>            &lt;Text style={styles.subtitle}&gt;Expo • NestJS • Supabase • Stack&lt;/Text&gt;<br><br>            &lt;Button<br>                title=&quot;Sign in with Google&quot;<br>                onPress={signInWithGoogle}<br>                loading={isLoading}<br>                fullWidth<br>                style={styles.button}<br>            /&gt;<br><br>            {error &amp;&amp; (<br>                &lt;Text style={styles.errorText}&gt;{error.message}&lt;/Text&gt;<br>            )}<br>        &lt;/View&gt;<br>    );<br>}</pre><h4>Private Routes</h4><p>The app uses Expo Router for navigation with protected routes:</p><pre>// In _layout.tsx for the tabs section<br>import { Redirect, Stack } from &#39;expo-router&#39;;<br>import { useAuth } from &#39;@/contexts/AuthContext&#39;;<br><br>export default function TabsLayout() {<br>    const { user, isLoading } = useAuth();<br><br>    // If the user isn&#39;t authenticated, redirect to the login screen<br>    if (!user &amp;&amp; !isLoading) {<br>        return &lt;Redirect href=&quot;/login&quot; /&gt;;<br>    }<br>    <br>    return (<br>        &lt;Tabs&gt;<br>            &lt;Tabs.Screen name=&quot;index&quot; options={{ title: &#39;Home&#39; }} /&gt;<br>            &lt;Tabs.Screen name=&quot;profile&quot; options={{ title: &#39;Profile&#39; }} /&gt;<br>        &lt;/Tabs&gt;<br>    );<br>}</pre><h3>Security Considerations</h3><p>The ENSS stack implements several security best practices:</p><ol><li><strong>JWT Token-based Authentication</strong>: Secure, stateless authentication using Supabase JWTs</li><li><strong>Backend Validation</strong>: NestJS guards verify tokens before allowing access to protected endpoints</li><li><strong>Row-Level Security</strong>: Supabase RLS ensures users can only access their own data</li><li><strong>Service Role Key Protection</strong>: Backend uses Supabase service role key securely without exposing it to clients</li><li><strong>HTTPS Communication</strong>: All API calls should use HTTPS in production</li></ol><h3>Performance Optimizations</h3><p>For optimal performance:</p><ol><li><strong>API Response Caching</strong>: Implement caching strategies for frequently-accessed data</li><li><strong>Lazy Loading</strong>: Use dynamic imports and React Suspense for code-splitting</li><li><strong>Optimized Database Queries</strong>: Ensure database queries are properly indexed</li><li><strong>Image Optimization</strong>: Use Expo’s image optimization tools for mobile assets</li></ol><h3>Deployment Considerations</h3><p>For deploying your ENSS stack application:</p><ol><li><strong>Backend (NestJS)</strong>:</li></ol><ul><li>Deploy to a Node.js-friendly platform like Vercel, Render, or AWS Elastic Beanstalk</li><li>Set up proper environment variables for production</li><li>Configure CORS to only allow requests from your mobile app</li></ul><p><strong>2. Mobile App (Expo)</strong>:</p><ul><li>Use EAS Build for creating native binaries</li><li>Configure over-the-air updates for quick fixes</li><li>Set up different environment configurations for staging and production</li></ul><h3>Extending the ENSS Stack</h3><p>The base template can be extended with:</p><ol><li><strong>State Management</strong>: Add Redux or Zustand for complex state</li><li><strong>UI Components</strong>: Integrate UI libraries like NativeBase or React Native Paper</li><li><strong>Testing</strong>: Set up Jest and React Testing Library for comprehensive tests</li><li><strong>CI/CD</strong>: Implement GitHub Actions or CircleCI for continuous integration</li></ol><h3>Conclusion</h3><p>The ENSS stack provides a robust foundation for building cross-platform applications with modern technologies. By combining Expo, NestJS, and Supabase, you get the benefits of:</p><ul><li>TypeScript throughout the entire stack</li><li>Secure authentication and data storage</li><li>Fast development iterations</li><li>Cross-platform compatibility</li><li>Scalable architecture</li></ul><p>Whether you’re building a personal project or a production application, the ENSS stack offers a developer-friendly experience without sacrificing performance or security.</p><h3>Resources</h3><p>Repository: <a href="https://github.com/season179/enss-stack-starter">https://github.com/season179/enss-stack-starter</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d688fe38d9c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Next.js App vs. Pages: How Accidental Complexity Pushed Me to Finally Choose the app Directory]]></title>
            <link>https://medium.com/@season.saw/next-js-app-vs-pages-how-accidental-complexity-pushed-me-to-finally-choose-the-app-directory-1776fbfa0b2c?source=rss-2cb7ca6a7cd7------2</link>
            <guid isPermaLink="false">https://medium.com/p/1776fbfa0b2c</guid>
            <category><![CDATA[supabase]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[nextjs-15]]></category>
            <dc:creator><![CDATA[Season Saw]]></dc:creator>
            <pubDate>Sat, 29 Mar 2025 01:54:08 GMT</pubDate>
            <atom:updated>2025-03-29T01:54:08.551Z</atom:updated>
            <content:encoded><![CDATA[<p>Let’s be honest, sometimes development involves a bit of “vibe coding” — piecing things together, following tutorials, and getting things working without <em>always</em> scrutinizing every underlying mechanism. That was me on a recent Next.js 15 project. I was building features, adding routes, integrating Supabase for backend services, and generally making progress.</p><p>Then I hit a wall. Not just a minor hiccup, but a build-stopping error message that left me scratching my head:</p><pre>./utils/supabase/server.ts<br>Error:   × You&#39;re importing a component that needs &quot;next/headers&quot;. That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-<br>  │ your-application/rendering/server-components<br>  │ <br>  │ <br>   ╭─[/Users/season/my-next-app/utils/supabase/server.ts:2:1]<br> 1 │ import { createServerClient } from &quot;@supabase/ssr&quot;;<br> 2 │ import { cookies } from &quot;next/headers&quot;;<br>   · ───────────────────────────────────────<br> 3 │ <br> 4 │ export const createClient = async () =&gt; {<br> 5 │   const cookieStore = await cookies();<br>   ╰────</pre><p>My first reaction was confusion. I <em>was</em> using features from the app directory elsewhere, so why was this utility file (utils/supabase/server.ts), which used cookies from next/headers to interact with Supabase on the server-side, suddenly complaining about the pages/ directory?</p><p>After digging through my codebase and tracing where this createClient function was being called, the slightly embarrassing truth emerged: I had <em>accidentally</em> created a hybrid Next.js setup. My Supabase server utility, designed with app directory capabilities in mind (like accessing cookies via next/headers), was being imported and used by a part of my application still living under the rules of the pages directory (perhaps an API route in pages/api/ or an older page component).</p><p>The error message was explicit: next/headers only works in Server Components, which are <em>not supported</em> in the pages/ directory. My unintentional hybrid setup wasn&#39;t just messy; it was causing direct conflicts between the two paradigms. This wasn&#39;t a strategic choice; it was a byproduct of my evolving understanding (or lack thereof) and &quot;vibe coding&quot; my way through integrating different pieces. It became crystal clear that this approach wasn&#39;t sustainable. I needed to actually <em>understand</em> the difference and make a conscious decision.</p><p>This error became the catalyst for some focused research. What exactly <em>was</em> the difference between pages and app, and why did mixing them lead to errors like this? My conclusion after digging in? It was time to clean house and commit fully to the <strong>app directory</strong>.</p><h4>The Accidental Hybrid Trap (Illustrated by an Error)</h4><p>My project had somehow ended up with:</p><ul><li>Server-side utilities (utils/supabase/server.ts) written using app directory APIs like next/headers.</li><li>Code operating within the pages directory context (like API routes or older page components) trying to <em>use</em> those utilities.</li></ul><p>The problem wasn’t just having two folders; it was the incompatible assumptions each paradigm makes:</p><ul><li><strong>API Mismatches:</strong> The app directory provides server-only APIs like cookies() from next/headers. The pages directory has its own ways of handling server-side logic (like getServerSideProps) and doesn&#39;t recognize these newer APIs. Trying to use one within the context of the other leads directly to errors like the one I encountered.</li><li><strong>Routing and Rendering Confusion:</strong> Which system was responsible for rendering the page or handling the API request that ultimately called my utility? The error showed the pages directory context was active where an app directory feature was expected.</li><li><strong>Debugging Nightmare:</strong> The error message pointed the way, but it highlighted how easily these conflicts can arise when the boundary between app and pages isn&#39;t clearly managed.</li></ul><p><em>(The rest of the blog post can continue as previously drafted, discussing the </em><em>pages directory, the </em><em>app directory, and the reasons for choosing </em><em>app, but now grounded in this specific, concrete error.)</em></p><p>How does that feel? It makes the “bug” much more specific and directly ties the technical limitation (next/headers in pages) to the decision-making process.</p><h4>Understanding the Old Guard: The pages Directory</h4><p>My research started with what I was more familiar with (or thought I was):</p><ul><li><strong>Structure:</strong> File-based routing where pages/about.js becomes /about. Simple.</li><li><strong>Data Fetching:</strong> Relies on getServerSideProps, getStaticProps, etc. – distinct functions exported from the page file.</li><li><strong>Layouts:</strong> Primarily handled globally via _app.js and _document.js. Nested or route-specific layouts require workarounds like the getLayout pattern, which I hadn&#39;t consistently used.</li></ul><p>The pages directory is proven and stable, but its limitations, especially around nested layouts and its client-side-first rendering approach, felt less aligned with modern patterns.</p><h4>Embracing the New: The app Directory</h4><p>This was the area I’d dabbled in without full comprehension. The key differentiators became much clearer:</p><ul><li><strong>React Server Components (RSCs):</strong> The game-changer. Components run on the server by default, minimizing client-side JS. Interactivity is opt-in with &#39;use client&#39;. This fundamentally changes how you think about data fetching and rendering.</li><li><strong>Native Nested Layouts:</strong> Solves the layout problem elegantly. layout.js files create persistent UI shells for specific route segments, preserving state and improving navigation performance naturally.</li><li><strong>Integrated Data Fetching:</strong> Often simpler, using async/await directly within Server Components.</li><li><strong>Built-in UI States:</strong> loading.js and error.js provide file-based conventions for handling loading and error states within segments.</li><li><strong>Server Actions:</strong> Streamline form submissions and mutations without manually creating API endpoints.</li></ul><h4>Why the app Directory Became the Clear Choice</h4><p>Once I understood the two systems properly, the benefits of standardizing on the app directory, especially after my debugging ordeal, were compelling:</p><ol><li><strong>Eliminating Accidental Complexity:</strong> The most immediate win. A single, unified system for routing, layouts, and data fetching removes the ambiguity and potential for conflicts that plagued my hybrid setup. No more guessing which paradigm is in control.</li><li><strong>Modern Features &amp; Performance:</strong> RSCs, native nested layouts, streaming, and Server Actions offer powerful tools for building faster, more efficient applications with a better developer experience. Why leave these on the table?</li><li><strong>Improved Developer Ergonomics:</strong> The built-in conventions (loading.js, error.js, layouts) reduce boilerplate and make structuring the application more intuitive (once learned).</li><li><strong>Future-Proofing:</strong> The app directory is the clear focus of Next.js development. Committing to it aligns the project with the framework&#39;s future direction.</li></ol><h4>Moving Forward: Intentional Structure</h4><p>My accidental foray into the hybrid pages/app world was a valuable, if frustrating, lesson. It highlighted the importance of understanding the foundational choices in a framework, rather than just &quot;vibe coding&quot; through them. The complexity and the bug it caused were direct results of not having a clear, intentional structure.</p><p>I’m now refactoring my project to use the app directory exclusively. The research born from fixing my bug gave me the clarity I needed. While there&#39;s a learning curve associated with the app directory&#39;s concepts (especially RSCs), the payoff in terms of reduced complexity, enhanced features, and future alignment feels undeniable. If you find yourself juggling both directories, especially unintentionally, I highly recommend taking the time to understand the differences and consider making a clean switch – your future self (and your debugger) might thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1776fbfa0b2c" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>