<?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: Isaac Addis</title>
    <description>The latest articles on DEV Community by Isaac Addis (@isaacaddis).</description>
    <link>https://dev.to/isaacaddis</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%2F2469422%2F0990f818-39f5-4736-b468-b77b5c089723.jpeg</url>
      <title>DEV Community: Isaac Addis</title>
      <link>https://dev.to/isaacaddis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isaacaddis"/>
    <language>en</language>
    <item>
      <title>A Complete Beginner's Guide to Item-Item Recommendations with Matrix Factorization and BPR</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Fri, 20 Feb 2026 15:55:19 +0000</pubDate>
      <link>https://dev.to/isaacaddis/a-complete-beginners-guide-to-item-item-recommendations-with-matrix-factorization-and-bpr-2g61</link>
      <guid>https://dev.to/isaacaddis/a-complete-beginners-guide-to-item-item-recommendations-with-matrix-factorization-and-bpr-2g61</guid>
      <description>&lt;p&gt;When I wanted to build an item-item recommendation system with just implicit ratings (e.g. viewed, clicked as opposed to explicit ratings which are things like ratings for a movie out of 5), Matrix Factorization (MF) with Bayesian Personalized Ranking (BPR) is what Opus 4.5 recommended I get started with. As it helps to understand these systems to identify and debug issues, I'll be giving a walkthrough of the crucial information I learned over the span of a month (on-and-off) until I had enough of an understanding to fix problems and evaluate model performance. I'm now switching to &lt;a href="https://making.lyst.com/lightfm/docs/home.html" rel="noopener noreferrer"&gt;LightFM&lt;/a&gt; which overcomes a cold-start problem and has better recommendation performance, but this knowledge should still be valuable whether choosing to use MF + BPR or LightFM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I used this algorithm to: given an item i, recommend K items that are similar to item i. The K items could be used as recommendations to item i but the task is strictly to find related items to i. Items can be things like Amazon items, Netflix movies, and Spotify songs. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Matrix Factorization + BPR Algorithm
&lt;/h2&gt;

&lt;p&gt;Matrix Factorization with BPR allows us to accomplish this in a series of steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an interaction matrix containing (user, item) pairs. If a user interacted with an item, the interaction matrix at (user, item) is 1. Omit items users don't interact with from the interaction matrix; these items (negative items) are sampled during the training loop.&lt;/li&gt;
&lt;li&gt;Use Xavier-initialization to randomly initialize user vectors and item vectors. The number of user vectors should be all the users who were assigned a row in the interaction matrix and the number of item vectors should be all the items that were assigned a column in the interaction matrix.&lt;/li&gt;
&lt;li&gt;Run a training loop. For all users and items interacted with by the user (not recommended, please see Edit 2),

&lt;ol&gt;
&lt;li&gt;Sample a (user, positive item i, negative item j) triplet. A positive item is an item the user interacted with and a negative item is an item the user did not interact with. &lt;/li&gt;
&lt;li&gt;Compute a term, x-hat_{ui}, as the dot product of user vector u with positive item i. Compute another term, x-hat_{uj}, as a dot product of user vector u with negative item vector j. x-hat_{uij} := x-hat_{ui} - x-hat_{uj}. The loss function is defined as &lt;code&gt;-Math.log(sigmoid(x-hat_{uij}) + 1e-10)&lt;/code&gt;. The 1e-10 is there to avoid computing &lt;code&gt;log(0)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Perform gradient descent. Store the values u, p_i, n_i then with a pre-defined learning rate lr and number of factors K, for every 0 &amp;lt;= k &amp;lt; K:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userFactor[k] += lr * (sigmoidNegDiff * (pi - ni) - regularization * u)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;posFactor[k] += lr * (sigmoidNegDiff * u - regularization * pi)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;negFactor[k] += lr * (-sigmoidNegDiff * u - regularization * ni)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Edit 1:&lt;/strong&gt; sigmoidNegDiff in my code is sigmoid(-diff) and diff is x-hat_{uij}.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit 2:&lt;/strong&gt; The BPR 2009 paper advises against sampling in this way. It suggests random sampling across all (user, positive item, negative item) pairs.&lt;/p&gt;

&lt;p&gt;We now have latent user vectors and latent item vectors with every entry being a factor representing something about the item (these aren't manually labelled). &lt;/p&gt;

&lt;p&gt;With cosine similarity (finds the dot product of two vectors normalized by the product of their magnitudes), it's possible to find the closest-matching item vectors as well as the closest-matching user vectors to a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intuition
&lt;/h2&gt;

&lt;p&gt;The dot product represents how much two vectors "agree" with each other – vector cells have a high dot product if their entries agree in positive/negative and magnitude.&lt;/p&gt;

&lt;p&gt;x_{uij} is a score representing whether user u prefers item i over item j. It's true if positive, otherwise it's negative. Since the sigmoid function transforms a number x into a probability between 0 and 1, &lt;code&gt;sigmoid(x_{uij}) = P(i &amp;gt; j | u)&lt;/code&gt;. If x_uij &amp;gt; 0, sigmoidNegDiff = sigmoid(-diff) is near 0 and the update to posFactor is minimal. The result is that incorrect preference rankings result in larger model parameter updates.&lt;/p&gt;

&lt;p&gt;Note that gradient descent is moving userFactor • posFactor (x_{ui}) away from userFactor • negFactor (x_{uj}). It makes x_{uij} positive; in other words, it makes the score x_{ui} greater than the score x_{uj}.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Issues and their Fixes
&lt;/h2&gt;

&lt;p&gt;I ran into co-exposure bias (an item j gets recommended for item i just because they are visible from the same page) implementing MF + BPR. An item from a trending page would only have other trending items as its recommendations. There are several mitigations for this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inverse Propensity Weighting: multiply the loss function by 1/P(exposure).&lt;/li&gt;
&lt;li&gt;Remove non-organic interactions from the interaction matrix.&lt;/li&gt;
&lt;li&gt;Add a bias term that absorbs general tendencies for users to prefer specific items (for example, popular products on the Amazon website).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance and Tuning
&lt;/h2&gt;

&lt;p&gt;Two metrics commonly used for evaluating MF + BPR model performance are Recall@K and HR@K (Hit-rate@K). Hit Rate@K, according to ChatGPT, is defined as "The fraction of users for whom at least one relevant item appears in the top-K recommendations." Recall@K is defined as "The fraction of a user’s relevant items that appear in the top-K recommendations."&lt;/p&gt;

&lt;p&gt;One way model testing is commonly done is Leave-One-Out. If a user interaction with items 1, 2, 3, and 4, it's possible to train the model with items 1-3 and verify that item 4 appears in the recommendations within K across all users (the aforementioned HR@K).&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this blog article and/or found it helpful. &lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>recsys</category>
    </item>
    <item>
      <title>Working Expo + pnpm Workspaces Configuration</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Wed, 14 Jan 2026 13:42:05 +0000</pubDate>
      <link>https://dev.to/isaacaddis/working-expo-pnpm-workspaces-configuration-4k2l</link>
      <guid>https://dev.to/isaacaddis/working-expo-pnpm-workspaces-configuration-4k2l</guid>
      <description>&lt;h3&gt;
  
  
  Working Expo + pnpm Workspaces Configuration
&lt;/h3&gt;

&lt;p&gt;This post outlines how I got pnpm workspaces to work in a monorepo containing React and React Native projects. I will explain key components in our pnpm workspaces configuration and walk through the errors I encountered trying to get this to work. A complete metro.config.js is attached to the bottom of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This didn't work when running more than one mobile app in the pnpm workspaces project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisite Knowledge
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In pnpm workspaces, every module's packages are stored at a top-level &lt;code&gt;node_modules/.pnpm&lt;/code&gt; folder (the pnpm virtual store).&lt;/li&gt;
&lt;li&gt;In individual project folders, &lt;code&gt;node_modules&lt;/code&gt; contains symlinks to this pnpm store.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Overview of our pnpm Workspaces Configuration
&lt;/h2&gt;

&lt;p&gt;We have a shared package in &lt;code&gt;packages/&lt;/code&gt;. We import this package using &lt;code&gt;"&amp;lt;package-name&amp;gt;": "*"&lt;/code&gt; in package.json.&lt;/p&gt;

&lt;p&gt;We use Turbo for running projects and installing dependencies efficiently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Errors I Encountered and Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Error: &lt;code&gt;Invalid hook call&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Some packages like React and React Native must be pinned to specific versions to avoid multiple instances of them running at the same time. This part of our &lt;code&gt;metro.config.js&lt;/code&gt; fixes this (Singleton Pinning):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt; &lt;span class="o"&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;react&lt;/span&gt;&lt;span class="dl"&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;react-native&lt;/span&gt;&lt;span class="dl"&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;expo&lt;/span&gt;&lt;span class="dl"&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;expo-router&lt;/span&gt;&lt;span class="dl"&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;expo-modules-core&lt;/span&gt;&lt;span class="dl"&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;expo-constants&lt;/span&gt;&lt;span class="dl"&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;@expo/metro-runtime&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extraNodeModules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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;acc&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;
  
  
  Error: Missing transitive dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Make sure to &lt;strong&gt;not&lt;/strong&gt; use &lt;code&gt;disableHierarchicalLookups: true&lt;/code&gt;. This will prevent pnpm from locating packages in the pnpm store. This part of our Metro config solved this problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;packages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/.pnpm&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unstable_enableSymlinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unstable_enablePackageExports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Resolve from app's node_modules first, then root .pnpm for transitive deps&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeModulesPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/.pnpm/node_modules&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Error: Missing non-transitive dependency
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Install the packages with &lt;code&gt;npx expo install&lt;/code&gt;. This will make sure compatible versions with your Expo version are used.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Getting pnpm workspaces to work with Expo requires careful Metro configuration. The key points are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Singleton Pinning&lt;/strong&gt; - Ensure React, React Native, and Expo packages resolve to single instances to avoid "Invalid hook call" errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Symlinks&lt;/strong&gt; - Configure Metro to follow symlinks into the pnpm store for transitive dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;npx expo install&lt;/code&gt;&lt;/strong&gt; - Always install Expo-related packages through the Expo CLI to ensure version compatibility.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Complete metro.config.js File
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;getDefaultConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo/metro-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;withNativeWind&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nativewind/metro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;wrapWithReanimatedMetroConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-reanimated/metro-config&lt;/span&gt;&lt;span class="dl"&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;projectRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__dirname&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;monorepoRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set EXPO_ROUTER_APP_ROOT to absolute path BEFORE config is created&lt;/span&gt;
&lt;span class="c1"&gt;// This ensures require.context resolves correctly with pnpm symlinks&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPO_ROUTER_APP_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appRoot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPO_ROUTER_IMPORT_MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync&lt;/span&gt;&lt;span class="dl"&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getDefaultConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchFolders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;packages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/.pnpm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unstable_enableSymlinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unstable_enablePackageExports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Resolve from app's node_modules first, then root .pnpm for transitive deps&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeModulesPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monorepoRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/.pnpm/node_modules&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="c1"&gt;// Pin singletons and expo packages to prevent duplicate instances and ensure resolution&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt; &lt;span class="o"&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;react&lt;/span&gt;&lt;span class="dl"&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;react-native&lt;/span&gt;&lt;span class="dl"&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;expo&lt;/span&gt;&lt;span class="dl"&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;expo-router&lt;/span&gt;&lt;span class="dl"&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;expo-modules-core&lt;/span&gt;&lt;span class="dl"&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;expo-constants&lt;/span&gt;&lt;span class="dl"&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;@expo/metro-runtime&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extraNodeModules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;singletons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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;acc&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="c1"&gt;// Add SVG support&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;babelTransformerPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-svg-transformer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetExts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetExts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceExts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceExts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Wrap with NativeWind, then Reanimated&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapWithReanimatedMetroConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withNativeWind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./global.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>expo</category>
      <category>reactnative</category>
      <category>monorepo</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building an LLM-powered Facebook Marketplace Bot</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Fri, 26 Dec 2025 21:23:50 +0000</pubDate>
      <link>https://dev.to/isaacaddis/building-an-llm-powered-facebook-marketplace-bot-2o54</link>
      <guid>https://dev.to/isaacaddis/building-an-llm-powered-facebook-marketplace-bot-2o54</guid>
      <description>&lt;p&gt;I was interested in trying out bot development in Ethiopia’s emerging tech industry by creating a bot that would monitor product listings on Facebook Marketplace. I succeeded in my attempts to pass through bot detection using a simple heuristic: mimic a real human. As I eventually discarded this bot (Facebook Marketplace already has a “Notify Me” feature), I am open to sharing how I developed this.&lt;/p&gt;

&lt;p&gt;Please note that this does violate Facebook ToS.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Overview
&lt;/h2&gt;

&lt;p&gt;Here’s a summary of our tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digital Ocean VPS running Ubuntu&lt;/li&gt;
&lt;li&gt;Better Stack for logs (we had to use raw HTTPS requests with helper functions – this wasn’t working with the &lt;code&gt;pino&lt;/code&gt; library)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pm2&lt;/code&gt; for process management (this makes it easy to run a script as an always-on background task)&lt;/li&gt;
&lt;li&gt;Slack for alerts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;better-sqllite3&lt;/code&gt; to track alerted listings (to avoid alerting about the same product twice)&lt;/li&gt;
&lt;li&gt;OpenAI /chat/completions API with gpt-4o-mini for filtering product listings&lt;/li&gt;
&lt;li&gt;TypeScript + OOP (I’ve found that bot development works really well with OOP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Something interesting we did was use LLMs to filter out listings unrelated to our product query. For example, when the bot searches for "iPhone 15"s, the LLM filters out listings for "iPhone 15 Pro"s. We used gpt-4o-mini for this because of its high speed and low cost and had perfect results.&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%2Fgwkf6iindi5r1jyg8coz.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%2Fgwkf6iindi5r1jyg8coz.png" alt="Bot alert showing an iPhone 13 Pro Max deal" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Challenges + Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Captcha Harvesting
&lt;/h3&gt;

&lt;p&gt;Note: being signed-in is not required for monitoring Facebook Marketplace&lt;/p&gt;

&lt;p&gt;Captchas prevent signing in directly through the bot. To get past this, I created a captcha harvester script that opened Puppeteer on my local computer and saved subsequent authentication cookies onto my local machine. With the &lt;code&gt;scp&lt;/code&gt; command, I moved the cookies onto my VPS. This works for getting past login walls on Facebook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proxy Rotation
&lt;/h3&gt;

&lt;p&gt;I’ve noticed that I needed to rotate proxies from our ProxyManager class when: &lt;br&gt;
No amount of captchas would get me signed-in&lt;br&gt;
I would suddenly get a login wall when trying to monitor products&lt;/p&gt;

&lt;h3&gt;
  
  
  Residential Proxies
&lt;/h3&gt;

&lt;p&gt;I used residential proxies to make it seem like traffic was coming from realistic IPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Puppeteer Stealth Mode
&lt;/h3&gt;

&lt;p&gt;LLMs suggest this. Something crucial from this plugin is the obscuring of &lt;code&gt;navigator.webdriver&lt;/code&gt;, something that identifies Puppeteer/Playwright usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Agent
&lt;/h3&gt;

&lt;p&gt;I configured the browser user agent to Windows: &lt;code&gt;await page.setUserAgent(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36);&lt;/code&gt; despite running on a Linux VPS. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create scripts with LLMs to debug things. For example, I needed to create a script to check if Facebook recognized my authentication cookies with the Puppeteer settings we use in the bot&lt;br&gt;
Avoid alerting on obvious scams by filtering out prices that seem too low&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approximating what a human user would do proved to be a good tactic for getting past bot protection on Facebook Marketplace.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Techniques used here to get around bot protection should be applicable to other services.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>My Nontraditional Stress Management Routine</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Mon, 20 Oct 2025 18:22:46 +0000</pubDate>
      <link>https://dev.to/isaacaddis/my-nontraditional-stress-management-routine-1gld</link>
      <guid>https://dev.to/isaacaddis/my-nontraditional-stress-management-routine-1gld</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Stress management is crucial to maintaining optimal cognitive performance, which is great to maximize as a programmer. While building my first product, Voyage, I gradually built a stress management routine that works for my 6-7 day workweeks. I refined this routine, thinking mostly in terms of percent reductions on my baseline cortisol levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Routine
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HIIT (High-Intensity Interval Training): 20-25% reduction to baseline stress levels (&lt;a href="https://www.frontiersin.org/journals/psychology/articles/10.3389/fpsyg.2021.643069/full" rel="noopener noreferrer"&gt;source&lt;/a&gt;). Weightlifting in addition to this should also provide an additional reduction to baseline stress levels.&lt;/li&gt;
&lt;li&gt;Laying down: this was my own stress reduction idea that I came up with (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/24966018/#:~:text=mathematical%20tasks,risk%20for%20physiological%20problems%20associated" rel="noopener noreferrer"&gt;this&lt;/a&gt; study shows that higher self-discipline/perfectionism can lead to physiological problems associated with stress such as burnout). &lt;a href="https://www.researchgate.net/publication/375894966_The_Efficacy_of_Yoga_Nidra_on_Stress_Anxiety_and_Aggression_Levels_in_School-Going_Children" rel="noopener noreferrer"&gt;This&lt;/a&gt; is the closest study I could find that shows a quantified effect of laying down (~7.6%). &lt;/li&gt;
&lt;li&gt;Following Cal Newport's advice: it's difficult to sustain high-performance work for many hours at a time so I work in Deep Work blocks. I intentionally take breaks and go for walks. &lt;/li&gt;
&lt;li&gt;Dedicated entertainment time (TV/movies/video games): &lt;a href="https://www.frontiersin.org/journals/psychiatry/articles/10.3389/fpsyt.2022.1052275/full" rel="noopener noreferrer"&gt;This&lt;/a&gt; study shows an association between having a passive leisure hobby and a 4.1 point reduction (on a scale of 0-100) in stress levels. This was the best data I could find on the topic.&lt;/li&gt;
&lt;li&gt;Taking half-days off, a single day off, or days off of coding specifically. Doing this has reset my stress levels when I notice signs of burnout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a summary of the most important things I do to manage my stress levels. I look to optimize baseline stress levels rather than accumulate high stress and reactively try to fix the stress. This has helped me prevent negative effects of burnout – bad sleep, loss of focus, and low motivation – and maximize my productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit (Oct 23, 2025)&lt;/strong&gt;: Added reference to a Frontiers in Psychiatry (2022) study showing a 4-point reduction in stress levels among healthcare professionals with passive-solitary leisure activities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit (Oct 25, 2025)&lt;/strong&gt;: Added a link to a study showing that high discipline demands can lead to burnout.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>wellness</category>
    </item>
    <item>
      <title>Zero-Downtime AWS Cognito User Pool Migration (User Migration Lambda Trigger)</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Sat, 04 Oct 2025 12:21:27 +0000</pubDate>
      <link>https://dev.to/isaacaddis/zero-downtime-aws-cognito-user-pool-migration-user-migration-lambda-trigger-alf</link>
      <guid>https://dev.to/isaacaddis/zero-downtime-aws-cognito-user-pool-migration-user-migration-lambda-trigger-alf</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Using the User Migration Lambda Trigger on a new Cognito user pool enables users to sign-in to a new user pool without having to create a new account or reset their password. The User Migration Lambda runs after failed sign-in attempts and attempts to sign in users against the old user pool during a migration. Following an open-source repository, this blog will guide how to migrate users to a new user pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to the repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Collaborne/migrate-cognito-user-pool-lambda" rel="noopener noreferrer"&gt;https://github.com/Collaborne/migrate-cognito-user-pool-lambda&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes I made to the repository
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;I made sure the &lt;code&gt;event.response.userAttributes&lt;/code&gt; object had keys matching the new user pool's signup attributes (with the same exact spelling). &lt;/li&gt;
&lt;li&gt;I added &lt;code&gt;["custom:&amp;lt;attribute name&amp;gt;"]&lt;/code&gt; to the &lt;code&gt;event.response.userAttributes&lt;/code&gt; response to add custom attributes to a user in the new user pool.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>cognito</category>
      <category>aws</category>
    </item>
    <item>
      <title>Free Mapbox SSO Configuration with Auth0</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Mon, 15 Sep 2025 15:49:28 +0000</pubDate>
      <link>https://dev.to/isaacaddis/free-mapbox-sso-configuration-with-auth0-1aj1</link>
      <guid>https://dev.to/isaacaddis/free-mapbox-sso-configuration-with-auth0-1aj1</guid>
      <description>&lt;p&gt;I was in search of a free way to let people access our Mapbox account without giving company credentials. From there, I found Mapbox SSO which I configured with Auth0 to enable SSO (single-sign on) access to our company’s Mapbox. &lt;/p&gt;

&lt;p&gt;System Overview:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A NextJS application has a link with a &lt;code&gt;redirect_url&lt;/code&gt; pointing to Mapbox’s provided single sign-on URL. The link is an href to the Auth0 application which is a login form. &lt;/li&gt;
&lt;li&gt;The Auth0 Mapbox SSO Integration provides fields to paste into Mapbox’s SSO configuration page&lt;/li&gt;
&lt;li&gt;The Auth0 application references an allowlist for users allowed to sign up using a pre-registration action&lt;/li&gt;
&lt;li&gt;On successful sign in, the SAML attribute role is set from user_metadata (this value is set during the pre-user-registration action) to be Admin. According to Mapbox’s SSO configuration page, “Users with the Admin role can read and write to all resources and APIs. They cannot access invoices, nor can they read or write to account settings.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Required Code
&lt;/h2&gt;

&lt;p&gt;Pre-user registration action (Auth0):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.onExecutePreUserRegistration = async (event, api) =&amp;gt; {
  // Define your allowlist of emails
  const allowlist = [
    (List of allowed emails)
  ];

  // Check if the user's email is in the allowlist
  if (!event.user.email || !allowlist.includes(event.user.email)) {
    // If the email is not in the allowlist, deny access
    const msg = 'Access denied: Your email is not in the allowlist.'
    api.access.deny(msg, msg);
  }
  api.user.setUserMetadata("role", "admin")
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Post-registration action (Auth0):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.onExecutePostLogin = async (event, api) =&amp;gt; {
  const role = event.user.user_metadata["role"]
  api.samlResponse.setAttribute("role", role)
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Link from the NextJS app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;a href="&amp;lt;domain name from the Mapbox application&amp;gt;/authorize?response_type=code&amp;amp;client_id=&amp;lt;client id from the Mapbox application&amp;gt;redirect_uri=&amp;lt;Mapbox-provided Single Sign-on URL&amp;gt;"&amp;gt;
    Click here to redirect to SSO for MapBox
  &amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: In the "Application URIs" section of the Mapbox application settings, I set these two values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application Login URI: NextJS application URL containing the redirect &lt;a&gt; tag&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Allowed Callback URLs: Mapbox-provided Single Sign-on URL&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>mapbox</category>
      <category>auth0</category>
      <category>sso</category>
    </item>
    <item>
      <title>Using React Native Components in a Next.js Web App (via @expo/next-adapter)</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Sat, 06 Sep 2025 14:26:03 +0000</pubDate>
      <link>https://dev.to/isaacaddis/using-react-native-components-in-a-nextjs-web-app-via-exponext-adapter-3497</link>
      <guid>https://dev.to/isaacaddis/using-react-native-components-in-a-nextjs-web-app-via-exponext-adapter-3497</guid>
      <description>&lt;h3&gt;
  
  
  Using React Native Components in a Next.js Web App (via @expo/next-adapter)
&lt;/h3&gt;

&lt;p&gt;This post documents how I imported a React Native component library into a Next.js pages‑router app and rendered it on the web using React Native Web, @expo/next-adapter, and Babel. This configuration is a viable option for preventing duplicate code across a mobile + NextJS app codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveat&lt;/strong&gt;: Our team at &lt;a href="https://voyagetraveltechnologiesplc.com" rel="noopener noreferrer"&gt;Voyage Travel Technologies PLC&lt;/a&gt; eventually decided on removing this. Using React Native components with NextJS resulted in the following problems for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slower build times&lt;/li&gt;
&lt;li&gt;build errors needed to be suppressed for &lt;code&gt;next build&lt;/code&gt; to work&lt;/li&gt;
&lt;li&gt;the web application was significantly slower in production&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Tech Stack
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15 (pages router) + React 19&lt;/li&gt;
&lt;li&gt;React Native Web&lt;/li&gt;
&lt;li&gt;Tailwind CSS v4 + NativeWind v4&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@expo/next-adapter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-native-css-interop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Babel via &lt;code&gt;.babelrc&lt;/code&gt; (Next disables SWC when a custom Babel config is present)&lt;/li&gt;
&lt;li&gt;Shared RN components (e.g., &lt;code&gt;@isaacaddis/private-rn-library&lt;/code&gt;), RN primitives (&lt;code&gt;@rn-primitives/*&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Key ideas
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Tailwind v4 uses a new import model: in &lt;code&gt;globals.css&lt;/code&gt; use &lt;code&gt;@import "tailwindcss";&lt;/code&gt; (not v3’s &lt;code&gt;@tailwind base; ...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;RN components don’t understand &lt;code&gt;className&lt;/code&gt; without CSS interop. Map the primitives you actually render.&lt;/li&gt;
&lt;li&gt;Many third‑party triggers/buttons are &lt;code&gt;Pressable&lt;/code&gt; under the hood — interop it specifically.&lt;/li&gt;
&lt;li&gt;Add all RN and shared packages to &lt;code&gt;transpilePackages&lt;/code&gt; or you’ll hit syntax/runtime errors.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;important: "html"&lt;/code&gt; so Tailwind wins over other stylesheets.&lt;/li&gt;
&lt;li&gt;NativeWind works in the pages router and "use client" routes; RSC support is in progress.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Integrating the React Native library
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install the library and its peer deps (example uses &lt;code&gt;@isaacaddis/private-rn-library&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Add the library to &lt;code&gt;transpilePackages&lt;/code&gt; so Next transpiles it for the browser.&lt;/li&gt;
&lt;li&gt;Add the library path to Tailwind &lt;code&gt;content&lt;/code&gt; so utility classes used inside it are generated.&lt;/li&gt;
&lt;li&gt;Ensure CSS interop covers the primitives the library renders (e.g., &lt;code&gt;View&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;TouchableOpacity&lt;/code&gt;, and especially &lt;code&gt;Pressable&lt;/code&gt; for triggers/buttons).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After these steps, components can be imported and used as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Dialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DialogTrigger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@isaacaddis/private-rn-library&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rounded-xl border p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Card content&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DialogTrigger&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-600 p-3 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-white"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Open&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DialogTrigger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h4&gt;
  
  
  next.config.ts
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withExpo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@expo/next-adapter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('next').NextConfig} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withExpo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reactStrictMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transpilePackages&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="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nativewind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-css-interop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rn-primitives&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@isaacaddis/private-rn-library&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-reanimated&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="na"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phosphor-react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phosphor-react&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  tsconfig.json (please note the &lt;code&gt;jsxImportSource&lt;/code&gt; line)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsxImportSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nativewind"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"preserve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bundler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"next-env.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nativewind-env.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"**/*.tsx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  .babelrc (Babel config)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"presets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"next/babel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@babel/preset-env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@babel/preset-flow"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@babel/plugin-transform-react-jsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"automatic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"importSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nativewind"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  nativewind-env.d.ts
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;reference types="nativewind/types" /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  tailwind.config.js (Tailwind v4 + NativeWind)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('tailwindcss').Config} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&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="s2"&gt;./pages/**/*.{ts,tsx,js,jsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/**/*.{ts,tsx,js,jsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./node_modules/@isaacaddis/private-rn-library/**/*.{ts,tsx,js,jsx}&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="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nativewind/preset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="na"&gt;important&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// tokens (colors, sizes, etc.)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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;h4&gt;
  
  
  postcss.config.mjs (Tailwind v4)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="s2"&gt;@tailwindcss/postcss&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  styles/globals.css (Tailwind v4 import)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  pages/_app.tsx (CSS interop for RN primitives)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/styles/globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cssInterop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-css-interop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TouchableOpacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Pressable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Map className -&amp;gt; style for primitives actually rendered in your app/libs&lt;/span&gt;
&lt;span class="nf"&gt;cssInterop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;cssInterop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;cssInterop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TouchableOpacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;cssInterop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// critical for many Trigger components&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AppProps&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Issues and fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No background colors rendering&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cause: Tailwind v3 directives used with Tailwind v4 → CSS never generated.&lt;/li&gt;
&lt;li&gt;Fix: Use &lt;code&gt;@import "tailwindcss";&lt;/code&gt; in &lt;code&gt;globals.css&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Border width shows, but &lt;code&gt;border-red-500&lt;/code&gt; doesn’t&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cause: Underlying component is &lt;code&gt;Pressable&lt;/code&gt; without CSS interop.&lt;/li&gt;
&lt;li&gt;Fix: &lt;code&gt;cssInterop(Pressable, { className: "style" })&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;SyntaxError / Unexpected tokens from node_modules&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cause: Untranspiled RN/shared packages.&lt;/li&gt;
&lt;li&gt;Fix: Add them to &lt;code&gt;transpilePackages&lt;/code&gt; and ensure the library ships browser‑compatible JS.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Styles present but overridden&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: Add &lt;code&gt;important: "html"&lt;/code&gt; to Tailwind config to increase specificity.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using @expo/next-adapter with React Native Web, Tailwind v4, NativeWind, react-native-css-interop, and Babel allows for importing a React Native library inside a Next.js web app without duplicating UI code. The required steps are: transpile React Native and the library, use Tailwind v4’s &lt;code&gt;@import&lt;/code&gt; CSS, include the library paths in Tailwind &lt;code&gt;content&lt;/code&gt;, and map React Native primitives (including &lt;code&gt;Pressable&lt;/code&gt;) with CSS interop so &lt;code&gt;className&lt;/code&gt; resolves to styles. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit (Oct 25, 2025)&lt;/strong&gt;: Added a caveat to the introduction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit (Nov 8, 2025)&lt;/strong&gt;: Updated the caveat for accuracy.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>expo</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>(EAS Custom Builds, Maestro Cloud) Expo + Maestro CI Pipeline Overviews</title>
      <dc:creator>Isaac Addis</dc:creator>
      <pubDate>Sat, 06 Sep 2025 06:39:56 +0000</pubDate>
      <link>https://dev.to/isaacaddis/expo-maestro-ci-pipeline-overviews-eas-custom-builds-maestro-cloud-142n</link>
      <guid>https://dev.to/isaacaddis/expo-maestro-ci-pipeline-overviews-eas-custom-builds-maestro-cloud-142n</guid>
      <description>&lt;h2&gt;
  
  
  Pipeline overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Push to main triggers a GitHub Action workflow&lt;/li&gt;
&lt;li&gt;The workflow installs dependencies, installs EAS CLI, then runs an EAS build with a &lt;code&gt;build-and-maestro-test&lt;/code&gt; profile&lt;/li&gt;
&lt;li&gt;Artifacts and logs are uploaded for debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: I led the creation of our mobile app CI pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  EAS Custom Builds vs Maestro Cloud (cost-driven choice)
&lt;/h2&gt;

&lt;p&gt;I implemented both EAS‑based and Maestro Cloud pipelines and validated they work&lt;/p&gt;

&lt;p&gt;We run on EAS Build because it's significantly cheaper. As of Aug 24, 2025: EAS Starter Plan starts at $19/month; Maestro Cloud is about $212.50/month&lt;/p&gt;

&lt;h2&gt;
  
  
  EAS Custom Builds Configuration
&lt;/h2&gt;

&lt;p&gt;Here is our &lt;code&gt;build-and-maestro-test&lt;/code&gt; configuration (eas.json):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"build-and-maestro-test": {
    "withoutCredentials": true,
    "config": "build-and-maestro-test.yml",
    "android": {
    "buildType": "apk",
    "image": "latest"
    },
    "ios": {
    "simulator": true,
    "image": "latest"
    },
    "channel": "build-and-maestro-test"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the corresponding Github Actions workflow file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: EAS Build &amp;amp; Maestro Tests

on:
  push:
    branches:
      - "main"
  pull_request:
    branches:
      - "main"

jobs:
  build_and_test:
    runs-on: ubuntu-latest
    timeout-minutes: 120
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install Bun
        uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install dependencies
        run: bun install

      - name: Install EAS CLI
        run: npm install -g eas-cli

      - name: Run EAS Build with Maestro Tests
        run: EXPO_TOKEN=${{ secrets.EXPO_TOKEN }} eas build --platform android --profile build-and-maestro-test --non-interactive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Maestro Cloud Configuration
&lt;/h2&gt;

&lt;p&gt;Here is the Github Actions workflow file for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: EAS Build &amp;amp; Maestro Tests

on:
  push:
    branches:
      - "**"

jobs:
  build_and_test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install Bun
        uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install dependencies
        run: bun install

      - name: Install EAS CLI
        run: npm install -g eas-cli

      - name: Fetch Latest EAS Build Artifact
        run: |
          # Export Expo token for authentication
          export EXPO_TOKEN=${{ secrets.EXPO_TOKEN }}
          # Get latest build URL
          BUILDS_JSON=$(eas build:list --platform android --profile production --status finished --json --non-interactive)
          # Validate JSON response
          if [[ -z "$BUILDS_JSON" || "$BUILDS_JSON" == "[]" ]]; then
            echo "No completed builds found!"
            exit 1
          fi
          # Extract the build URL
          BUILD_URL=$(echo "$BUILDS_JSON" | jq -r '.[0].artifacts.buildUrl')
          # Ensure a valid URL was extracted
          if [[ -z "$BUILD_URL" || "$BUILD_URL" == "null" ]]; then
            echo "No valid build URL found!"
            exit 1
          fi
          echo "Downloading APK from $BUILD_URL"
          # Download the APK using Expo authentication
          curl -L -o app-release.apk $BUILD_URL
          echo "APK downloaded to app-release.apk"
          ls -la

      - name: Install Maestro CLI
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "$HOME/.maestro/bin" &amp;gt;&amp;gt; $GITHUB_PATH

      - name: Run Maestro Cloud Tests on APK
        uses: mobile-dev-inc/action-maestro-cloud@v1.9.6
        with:
          api-key: ${{ secrets.MAESTRO_API_KEY }}
          project-id: ${{ secrets.PROJECT_ID }}
          app-file: app-release.apk
          workspace: maestro
          android-api-level: 33
          env: |
            MAESTRO_TEST_EMAIL=${{ secrets.MAESTRO_TEST_EMAIL }}
            MAESTRO_TEST_PASSWORD=${{ secrets.MAESTRO_TEST_PASSWORD }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips and tricks from our experience
&lt;/h2&gt;

&lt;p&gt;These tests can be flaky:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add short delays before interacting with components in dialogs&lt;/li&gt;
&lt;li&gt;Use pixel coordinates when IDs aren't reliable&lt;/li&gt;
&lt;li&gt;Use runScript for dynamic data&lt;/li&gt;
&lt;li&gt;I tested locally on a Pixel 5 emulator (API 33)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>expo</category>
      <category>maestro</category>
      <category>cicd</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
