<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel xmlns:atom="http://www.w3.org/2005/Atom">
   <atom:link href="https://theodortomas.com/feed.xml" rel="self" type="application/rss+xml" />
        <title>Theodór Tómas – Software Engineering and Development Insights</title>
        <link>https://theodortomas.com</link>
        <description>Exploring software engineering, building scalable applications, and crafting innovative solutions by Theodór Tómas.</description>
        <lastBuildDate>Fri, 28 Aug 2026 19:38:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://theodortomas.com</generator>
        <image>
            <title>Theodór Tómas – Software Engineering and Development Insights</title>
            <url>https://theodortomas.com/avatar.png</url>
            <link>https://theodortomas.com</link>
        </image>
        <copyright>© 2026 Theodór Tómas. All rights reserved.</copyright>
        <item>
            <title><![CDATA[How I Built a $0/Month Screenshot + Web-Scraper Pipeline]]></title>
            <link>https://theodortomas.com/articles/zero-cost-screenshot-scraper</link>
            <guid>https://theodortomas.com/articles/zero-cost-screenshot-scraper</guid>
            <pubDate>Thu, 01 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical, serverless pipeline for screenshots and web scraping—running entirely on free tiers. Learn how to combine Vercel, Browserless, Neon, QStash, Inngest, and more for a zero-cost, production-ready workflow.]]></description>
            <content:encoded><![CDATA[<p>## How I Built a $0/Month Screenshot + Web-Scraper Pipeline</p><p>When you’re bootstrapping or indie hacking, every dollar counts. SaaS subscriptions and cloud bills can eat into your runway fast. That’s why I set out to build a **fully serverless screenshot and web-scraper pipeline**—with zero monthly cost, using only free tiers.</p><p>This article breaks down the architecture, code, and lessons learned from running a production-grade pipeline for screenshots, scraping, and reports—without paying a cent.</p><p>---</p><p>## TL;DR</p><p>You can build a robust, automated screenshot and scraping pipeline for free using:</p><p>- **QStash** for scheduling and queueing jobs - **Inngest** for orchestration and retries - **Puppeteer + Browserless** for headless Chrome screenshots - **Vercel Blob** for free file storage - **Neon + Prisma** for Postgres metadata</p><p>All running on Vercel’s Hobby plan, with no always-on servers.</p><p>---</p><p>## 🛠️ Why Serverless? Why Free?</p><p>For indie hackers, cost and simplicity are everything:</p><p>- **No idle servers:** Functions only run when needed. - **No patching or maintenance:** Providers handle the heavy lifting. - **Free quotas:** Each service offers a generous free tier—combine them for a $0 bill.</p><p>---</p><p>## Architecture Overview</p><p>```mermaid graph TD A[QStash request] -->|POST| B[/api/analysis/process] B --> C{Inngest steps} C --> D[Vercel Fn → Puppeteer via Browserless] D --> E[Vercel Blob Storagedesktop.png / mobile.png] D --> F[Prisma → Neoncapture metadata] ```</p><p>---</p><p>## 1. Queue & Schedule with QStash</p><p>```js</p><p>const qstash = new Client({ token: process.env.QSTASH_TOKEN })</p><p>await qstash.publishJSON({ topic: 'on-demand-analytics', body: { url: 'https://site.dev', email: 'owner@site.dev' }, }) ```</p><p>> **Why QStash instead of DIY cron?** > QStash gives you a managed webhook queue with built‑in retries and cron scheduling on Upstash’s free tier (500 requests **per day**, far more than you’ll need for your usage). No Redis instance, no CloudWatch rules—just publish JSON and let QStash worry about persistence and back‑off.</p><p>- **Free quota:** 500 requests/day + cron at no cost.</p><p>---</p><p>## 2. Event Orchestration with Inngest</p><p>```js</p><p>export default inngest.createFunction( { id: 'scrape' }, { event: 'analysis.triggered' }, async ({ event, step }) => { const { url, email } = event.data</p><p>const data = await step.run('scrape-site', () => scrapeSite(url)) const record = await step.run('store-meta', () => saveCapture(url, data)) }, ) ```</p><p>Inngest also allows you to bypass typical serverless timeouts for longer jobs by splitting them into multiple steps, enabling advanced website scraping and user interactions that take more time to complete. For my analytical tools, this enables more complex screenshot analysis without hitting function limits.</p><p>- **Free:** 100k invocations/month.</p><p>---</p><p>## 3. Scrape & Screenshot with Puppeteer + Browserless</p><p>```js</p><p>export async function scrapeSite(url) { const browser = await puppeteer.connect({ browserWSEndpoint: `wss://chrome.browserless.io?token=${process.env.BROWSERLESS_TOKEN}`, })</p><p>const page = await browser.newPage() await page.setViewport({ width: 1440, height: 900 }) await page.goto(url, { waitUntil: 'networkidle0', timeout: 45000 }) const desktop = await page.screenshot({ fullPage: true })</p><p>await page.setViewport({ width: 390, height: 844, isMobile: true }) await page.reload({ waitUntil: 'networkidle0' }) const mobile = await page.screenshot({ fullPage: true })</p><p>await browser.close()</p><p>// store in Vercel Blob (public URL) const desktopUrl = (await put(`${Date.now()}-desk.png`, desktop)).url const mobileUrl = (await put(`${Date.now()}-mob.png`, mobile)).url</p><p>return { desktopUrl, mobileUrl } } ```</p><p>- **Browserless free:** 3 concurrent sessions / 1,000 sec daily - **Vercel Blob free:** 100 GB storage, 100 GB egress</p><p>---</p><p>## 4. Metadata into Neon Postgres via Prisma</p><p>```txt model Capture { id          Int      @id @default(autoincrement()) url         String desktopShot String mobileShot  String createdAt   DateTime @default(now()) } ```</p><p>```js</p><p>export function saveCapture(url, shots) { return prisma.capture.create({ data: { url, desktopShot: shots.desktopUrl, mobileShot: shots.mobileUrl, }, }) } ```</p><p>- **Neon free:** 10 GB storage, generous row limits</p><p>---</p><p>## Cost Table @ Vercel Hobby Tier</p><p>| Service          | Free Tier                           | | ---------------- | ----------------------------------- | | Vercel Functions | 100k invocations + 100 GB-hr/month  | | Vercel Blob      | 1 GB storage, 10 GB egress, 10k ops | | Browserless      | 1,000 units, 1 concurrency, 1-min   | | QStash           | 500 msgs/day, 100 RPS               | | Inngest          | 50k runs, concurrency=5             | | Neon             | 0.5 GB, 191.9 compute-hr, 5 GB eg.  |</p><p>**Total monthly bill: $0.**</p><p>---</p><p>## Updated Implementation Notes</p><p>You can combine Vercel’s Hobby plan, a free Browserless token, QStash, and Neon DB to handle scraping and screenshots at zero cost. Each service stays within free tiers under typical usage. By connecting Puppeteer to Browserless, storing images on Vercel Blob, and triggering workflows through QStash and Inngest, the entire process runs serverless with minimal setup or maintenance.</p><p>---</p><p>## Lessons Learned</p><p>1. Single Browserless session, many tabs → stay under free concurrency. 2. Blob Storage beats S3 for DX → `put()` returns a url instantly. 3. Inngest retries → isolate failure; one step re-runs without duplicating emails. 4. Prisma + Neon → serverless driver means no “max connections” pain. 5. Vercel Hobby is plenty → tasks barely scratch 2% of the compute quota.</p><p>---</p><p>## Result</p><p>This pipeline now:</p><p>- Runs entirely on free‑tier, server‑less services—no idle servers, no surprise bills. - Captures full‑page desktop and mobile screenshots, stores them on Vercel Blob, and logs metadata in Neon. - Completes each job in under 30 seconds of function runtime.</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
        <item>
            <title><![CDATA[How to Stand Out as a Software Engineer in 2025: Three Career Paths to Stay Relevant in the New AI Age]]></title>
            <link>https://theodortomas.com/articles/software-engineering-career-trends-2025</link>
            <guid>https://theodortomas.com/articles/software-engineering-career-trends-2025</guid>
            <pubDate>Wed, 29 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Explore three emerging career paths in software engineering and how they can help you stay ahead in an evolving industry.]]></description>
            <content:encoded><![CDATA[<p>## Overview</p><p>The tech industry is evolving rapidly, and with it, the expectations for software engineers are shifting. Competition for jobs is higher than ever, AI is automating repetitive tasks, and companies are looking for developers who bring more to the table than just writing code.</p><p>That doesn’t mean specialists will disappear—far from it. If you’re deeply skilled in a particular domain, there will always be demand for your expertise. However, even specialists need to evolve, incorporating AI and automation where relevant.</p><p>For those looking to grow their careers, three distinct trends are emerging—particularly for UI engineers, front-end-focused developers, back-end engineers, and even full-stack engineers who want to future-proof their skill sets. These three roles aren’t just about survival; they’re about standing out in an increasingly crowded job market.</p><p>So, if you’re wondering what to focus on in the coming years, here are three career paths that are becoming more prevalent—and why they matter.</p><p>---</p><p>## 1. The Product Engineer: Bridging Development and Ownership</p><p>### (A developer who can think like a product owner and prototype ideas fast)</p><p>- More companies are valuing engineers who can think beyond code and understand business needs. - Startups, especially, love engineers who can build the first version of a product, iterate quickly, and even contribute to product strategy.</p><p>### What this role looks like in practice:</p><p>- You understand **customer needs** and business goals. - You can **prototype an idea quickly** (whether in Figma, Webflow, or code). - You work closely with designers, marketers, and product teams to **ship fast and refine based on feedback**.</p><p>### Why this is valuable:</p><p>- Startups often don’t have dedicated product managers in the early stages. - Being able to translate an idea into an interactive prototype is a huge advantage. - You don’t need to become a full-fledged product manager, but knowing how to **turn ideas into working experiences** makes you incredibly valuable.</p><p>🚀 **Who should go for this?**</p><p>- Front-end or back-end developers who enjoy **product thinking, rapid prototyping, and iterating based on user feedback**. - Developers who want to work **closer to business decisions and user needs** rather than just writing features from Jira tickets.</p><p>---</p><p>## 2. The Design Engineer: Merging UI Design With Front-End Engineering</p><p>### (A front-end engineer who can also design, or a designer who can also code)</p><p>- The barrier between designers and developers is shrinking. More companies expect engineers to handle UI work that previously required a separate designer. - This role is especially valuable in small teams and startups, where hiring a separate UX/UI designer isn’t always possible.</p><p>### What this role looks like in practice:</p><p>- You can **design and implement** a user interface, going from Figma to production code yourself. - You understand **design principles**, accessibility, and UI best practices. - You can work in tools like **Figma, Adobe XD, or even Webflow** while still being a strong coder.</p><p>### Why this is valuable:</p><p>- Reduces the gap between design and development, making teams more efficient. - Companies love engineers who can **make design decisions on their own** rather than waiting for handoffs. - Modern UI frameworks (like Tailwind, Radix, and component libraries) make it easier to bridge this gap than ever before.</p><p>🎨 **Who should go for this?**</p><p>- Engineers who enjoy **design, visuals, animations, and front-end performance**. - Designers who want to expand their **technical skill set** and become more independent.</p><p>---</p><p>## 3. The Cloud-Enabled Engineer: Mastering the Infrastructure Behind Software</p><p>### (A developer who understands cloud infrastructure and scalable deployment)</p><p>- The **cloud industry is growing at 16% per year** and will surpass **$1 trillion by 2030**. - Knowing how to deploy and manage infrastructure **sets you apart** from other front-end or back-end developers. - Companies want engineers who understand **serverless, CDNs, API gateways, and cost optimization**.</p><p>### What this role looks like in practice:</p><p>- You’re comfortable with **AWS, GCP, or Azure** (even at a basic level). - You know how to **deploy apps efficiently**, whether via Vercel, Netlify, or custom cloud setups. - You understand **serverless computing, edge functions, and API scaling strategies**.</p><p>### Why this is valuable:</p><p>- Full-stack engineers who know cloud infrastructure **can build, deploy, and scale applications with minimal DevOps dependency**. - Even front-end engineers benefit from knowing **how their apps are deployed, optimized, and secured**. - AI-powered infrastructure management is becoming more common, and knowing cloud concepts allows you to integrate **cost-saving, automated deployment strategies**.</p><p>☁️ **Who should go for this?**</p><p>- Full-stack engineers who want to **expand into DevOps, serverless computing, or scalable web architecture**. - Developers who want to be more **self-sufficient** rather than waiting on a DevOps engineer for every deployment.</p><p>---</p><p>## Final Thoughts: Stand Out, Don’t Blend In</p><p>The competition in software engineering is **tougher than ever**, and the best way to stand out is by evolving your skill set. That doesn’t mean becoming a “jack of all trades” and being mediocre at everything—it means choosing **one or two adjacent skills** that make you uniquely valuable.</p><p>If you’re a **front-end engineer**, consider learning **product ownership or design**. If you’re a **back-end engineer**, consider **cloud infrastructure** to boost your career.</p><p>💡 **My Plan:**</p><p>- I’ll continue **honing my front-end and back-end skills** while expanding into **cloud infrastructure**—not just because it's a growing field, but because I want to become a more powerful engineer. Mastering cloud allows me to solve different kinds of problems, build diverse applications, and integrate across multiple domains. For me, this is the best way to grow and push my capabilities forward. - If you’re figuring out your next move, pick one of these paths and **start learning today**—because staying still in tech means getting left behind.</p><p>And if all else fails? Well, there’s always the option of creating a van-life YouTube channel.</p><p>— **Tómas**</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
        <item>
            <title><![CDATA[Monorepos: One Repo to Rule Them All (From Indie Hackers to Billion-Dollar Unicorns)]]></title>
            <link>https://theodortomas.com/articles/monorepos-for-indie-hackers-and-startups</link>
            <guid>https://theodortomas.com/articles/monorepos-for-indie-hackers-and-startups</guid>
            <pubDate>Mon, 09 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Explore the benefits and drawbacks of adopting a monorepo—whether you’re a solo indie hacker or a startup scaling toward a billion-dollar valuation. Learn how a single repository can simplify your development workflow, reduce costs, and set you up for growth.]]></description>
            <content:encoded><![CDATA[<p>## TL;DR</p><p>Monorepos consolidate all your code—frontends, backends, and shared libraries—into one repository. This simplifies how you **build, deploy, and maintain** multiple projects. Whether you're a **solo indie hacker** or a **scaling startup**, a monorepo can cut overhead and streamline your workflow.</p><p>---</p><p>## 🛠️ **For Indie Hackers: Efficiency Made Simple**</p><p>### **Pros of Using a Monorepo**</p><p>1. **One Repo to Manage Everything** Keep dependencies, tests, and deployments in one place. No more switching between multiple repositories.</p><p>2. **Effortless Code Sharing** Share components and utilities between projects without duplicating code.</p><p>3. **Unified Tooling and Automation** Centralize CI/CD pipelines, linters, and test runners for consistent workflows.</p><p>4. **Reduced Costs and Cognitive Load** Simplify infrastructure and reduce the mental overhead of juggling multiple repos.</p><p>5. **Faster Product Launches** Spin up new projects quickly using shared resources and configurations.</p><p>---</p><p>### **Visualizing the Difference**</p><p>```bash Monorepo:               Multi-Repo: Root Directory          Project A Repo ├── App A               Project B Repo ├── App B               Project C Repo └── Shared Components   └── Shared Components (duplicated) ```</p><p>In a **monorepo**, everything is streamlined. In a **multi-repo**, managing shared code can quickly become tedious.</p><p>---</p><p>## 🚀 **For Startups Scaling to Billion-Dollar Unicorns**</p><p>### **Why Companies Like Google and Meta Use Monorepos**</p><p>1. **Seamless Growth** Scale your team and codebase without fragmenting projects.</p><p>2. **Simplified Dependency Management** Update shared libraries in one place, ensuring consistency across all projects.</p><p>3. **Efficient Refactoring** Make changes across multiple projects in a single commit.</p><p>4. **Improved Collaboration** Teams can share and reuse code effortlessly.</p><p>5. **Unified Tooling** Standardize CI/CD pipelines, linters, and tests for smoother development.</p><p>---</p><p>## 🤝 **Potential Downsides to Consider**</p><p>- **Initial Complexity** Setting up a monorepo can be challenging if you're new to the approach.</p><p>- **Performance Issues** Without proper caching, large monorepos can lead to longer build times.</p><p>- **Team Adjustment** Teams used to separate repos may need time to adapt to shared ownership.</p><p>**Tip:** Tools like **Nx**, **Turborepo**, and **Bazel** can help with caching, incremental builds, and easier configuration.</p><p>---</p><p>## 🔧 **Tooling Tips: Fast-Track Your Monorepo Setup**</p><p>- **Frameworks & Tools:** Use **Nx**, **Turborepo**, or **Bazel** for efficient monorepo management. They provide pre-configured templates, shared libraries, and CI/CD pipelines.</p><p>- **Testing & Quality Assurance:** Adopt a single test runner (like **Jest**) and a shared **ESLint** config for consistency.</p><p>- **CI/CD Integration:** Use one pipeline with caching and parallelization for faster builds and deployments.</p><p>---</p><p>## 🎯 **Real-World Example**</p><p>You’ve built a successful SaaS—**App A**—and want to launch **App B** to expand your business. With a monorepo:</p><p>- **Shared Libraries:** Both apps use the same design system and utilities. - **Centralized Updates:** Update a shared component once and both apps benefit. - **Unified Deployment:** Deploy both apps from the same pipeline.</p><p>---</p><p>## 🌱 **From Side Projects to Scaling Startups**</p><p>Monorepos help you transition from small projects to scalable applications by reducing complexity and fostering collaboration.</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
        <item>
            <title><![CDATA[From Basics to Advanced: A Step-by-Step Guide to Implementing Feature Flags Without External Services]]></title>
            <link>https://theodortomas.com/articles/feature-flags-at-zero-cost</link>
            <guid>https://theodortomas.com/articles/feature-flags-at-zero-cost</guid>
            <pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover how environment-driven feature flags can reduce costs, streamline development, and provide robust control over application features, including A/B testing, role-based access, and percentage rollouts.]]></description>
            <content:encoded><![CDATA[<p>## Introduction</p><p>Environment-driven feature flags offer a cost-effective, reliable, and scalable method for managing application features. By leveraging environment variables, you can toggle features across different environments—such as development, staging, and production—to support controlled rollouts, A/B testing, seamless deployments, and iterative development. This approach optimizes performance and ensures consistency.</p><p>While this guide focuses on implementing feature flags using Typescript in React and Next.js projects, the concepts and strategies discussed can be adapted to other frameworks and technologies, making it a versatile resource for any developer.</p><p>**Key benefits include:**</p><p>- **Optimize Performance**: Disabled features are stripped from the final build, enhancing load times and security. - **Environment-Specific Configurations**: Tailor features for production, staging, or testing environments without code changes. - **Eliminate Costs**: Remove the need for external services or runtime API calls—everything is built into your environment. - **Ensure Consistency**: Deterministic behavior reduces runtime errors and simplifies debugging. - **Support Advanced Use Cases**: Extend flags for A/B testing, role-based features, and percentage rollouts. - **Unified Context**: Seamlessly work across server and client components.</p><p>This guide covers the foundational setup for environment-driven feature flags and advanced configurations for progressive rollouts, A/B testing, and role-based access control.</p><p>---</p><p>## Handling Feature Flags in Server and Client Components</p><p>Feature flag implementations differ between client and server components due to the environment and rendering context. Below are the recommended approaches for each:</p><p>### In Client Components: Use a Context Provider and Custom Hook</p><p>In client components, feature flags should be passed through a provider and accessed via a custom hook. This ensures reliable and centralized management, avoiding issues with accessing environment variables directly in the browser.</p><p>**Set Up the Feature Flag Context and Hook:**</p><p>```typescript 'use client';</p><p>export type FeatureFlags = Record;</p><p>const FeatureFlagContext = createContext(undefined);</p><p>interface FeatureFlagProviderProps { children: React.ReactNode; featureFlags: FeatureFlags; }</p><p>export const FeatureFlagProvider: React.FC = ({ children, featureFlags, }) => { return (  {children}  ); };</p><p>export function useCheckIsFeatureEnabled(): (featureName: string) => boolean { const featureFlags = useContext(FeatureFlagContext); if (featureFlags === undefined) { throw new Error( 'useCheckIsFeatureEnabled must be used within a FeatureFlagProvider', ); }</p><p>return (featureName) => { const flagValue = featureFlags[featureName]; return flagValue === 'true' || flagValue === true; }; } ```</p><p>**Providing Feature Flags in the App:**</p><p>```typescript</p><p>export default function App() { const featureFlags = { FF_NEW_DASHBOARD: process.env.NEXT_PUBLIC_FF_NEW_DASHBOARD === 'true', FF_BETA_FEATURE: process.env.NEXT_PUBLIC_FF_BETA_FEATURE === 'true', };</p><p>return (    ); } ```</p><p>**Usage in Client Components:**</p><p>```typescript 'use client';</p><p>export function Dashboard() { const isFeatureEnabled = useCheckIsFeatureEnabled();</p><p>return (  {isFeatureEnabled('FF_NEW_DASHBOARD') ? (  ) : (  )}  ); } ```</p><p>### In Server Components: Use a Wrapper Component or Direct Method</p><p>In server components, you have two options for checking feature flags:</p><p>1. **Using the `FeatureEnabled` Wrapper Component** 2. **Using the `checkIsFeatureFlagEnabled` Method Directly**</p><p>#### Option 1: Using the `FeatureEnabled` Wrapper Component</p><p>**Implementing the Wrapper Component:**</p><p>```typescript</p><p>interface FeatureEnabledProps { featureFlag: string | string[]; children: React.ReactNode; }</p><p>function checkIsFeatureFlagEnabled(featureFlag: string): boolean { return process.env[featureFlag] === 'true' || process.env[featureFlag] === true; }</p><p>export function FeatureEnabled({ featureFlag, children, }: FeatureEnabledProps) { const isEnabled = Array.isArray(featureFlag) ? featureFlag.some((flag) => checkIsFeatureFlagEnabled(flag)) : checkIsFeatureFlagEnabled(featureFlag);</p><p>return isEnabled ? <>{children} : null; } ```</p><p>**Usage with the Wrapper Component:**</p><p>```typescript</p><p>export function Dashboard() { return (         ); } ```</p><p>#### Option 2: Using the `checkIsFeatureFlagEnabled` Method Directly</p><p>**Implementing the `checkIsFeatureFlagEnabled` Function:**</p><p>```typescript export function checkIsFeatureFlagEnabled(featureFlag: string): boolean { return ( process.env[featureFlag] === 'true' || process.env[featureFlag] === true ) } ```</p><p>**Usage with Direct Method:**</p><p>```typescript</p><p>export function Dashboard() { const isNewDashboardEnabled = checkIsFeatureFlagEnabled('FF_NEW_DASHBOARD'); const isOldDashboardEnabled = checkIsFeatureFlagEnabled('FF_OLD_DASHBOARD');</p><p>return (  {isNewDashboardEnabled && } {isOldDashboardEnabled && }  ); } ```</p><p>**Choosing Between the Two Methods:**</p><p>- **Use the Wrapper Component (`FeatureEnabled`)** when you prefer to wrap components declaratively, making your JSX cleaner and more readable. - **Use the Direct Method (`checkIsFeatureFlagEnabled`)** when you need more control within the component logic or when conditional rendering is more complex.</p><p>---</p><p>## Implementing Simple Feature Flags</p><p>### Defining Feature Flags in Environment Files</p><p>Create environment-specific files to define your feature flags:</p><p>```bash # .env.production FF_NEW_DASHBOARD=false</p><p># .env.staging FF_NEW_DASHBOARD=true ```</p><p>For client-side access, prefix the variables with `NEXT_PUBLIC_`:</p><p>```bash # .env.production NEXT_PUBLIC_FF_NEW_DASHBOARD=false ```</p><p>### Accessing Feature Flags in Code</p><p>**Server-Side Access:**</p><p>```typescript export function isFeatureEnabled(feature: string): boolean { return process.env[feature] === 'true' || process.env[feature] === true } ```</p><p>**Client-Side Access via Provider:**</p><p>In client components, use the `FeatureFlagProvider` and `useCheckIsFeatureEnabled` hook to access feature flags, as described in the previous section.</p><p>---</p><p>## Advanced Feature Flagging</p><p>Extend feature flags to support **A/B testing**, **percentage-based rollouts**, and **user-based targeting**, such as **role-based features**.</p><p>### Role-Based Feature Flags</p><p>Role-based feature flags allow you to enable or disable features for specific user roles, such as `admin`, `editor`, or `premiumUser`. This approach is useful when you want to grant access to features based on user permissions or subscription levels.</p><p>#### Implementing Role-Based Feature Flags</p><p>**Defining Feature Configurations:**</p><p>```typescript interface FeatureConfig { roles?: string[] }</p><p>const featureFlags: { [key: string]: FeatureConfig } = { FF_ADVANCED_DASHBOARD: { roles: ['admin', 'manager'], }, FF_PREMIUM_CONTENT: { roles: ['premiumUser'], }, } ```</p><p>**Function to Check Role-Based Access:**</p><p>```typescript function isFeatureEnabledForRole( featureName: string, userRole: string, ): boolean { const featureConfig = featureFlags[featureName]</p><p>if (!featureConfig || !featureConfig.roles) { // Feature is not defined or not restricted by roles return false }</p><p>return featureConfig.roles.includes(userRole) } ```</p><p>#### Using in Components</p><p>**Server Component Example:**</p><p>```typescript</p><p>export function RoleBasedFeature({ userRole }: { userRole: string }) { const isEnabled = isFeatureEnabledForRole('FF_ADVANCED_DASHBOARD', userRole);</p><p>return isEnabled ?  : ; } ```</p><p>**Client Component Example with Provider:**</p><p>In client components, pass the user role through context or props and use it within your hook or component logic.</p><p>```typescript 'use client';</p><p>export function RoleBasedFeature({ userRole }: { userRole: string }) { const isFeatureEnabled = useCheckIsFeatureEnabled();</p><p>const isEnabled = isFeatureEnabled('FF_ADVANCED_DASHBOARD') && isFeatureEnabledForRole('FF_ADVANCED_DASHBOARD', userRole);</p><p>return isEnabled ?  : ; } ```</p><p>### Combining Multiple Feature Flagging Strategies</p><p>You can combine role-based flags with percentage-based rollouts and A/B testing for more granular control.</p><p>#### Percentage-Based Rollouts</p><p>**Determining Feature Availability:**</p><p>```typescript</p><p>function isFeatureEnabledForUser( userId: string, featureName: string, rolloutPercentage: number, ): boolean { if (!userId || !featureName) { console.error('Invalid user ID or feature name') return false } const hashInput = `${featureName}-${userId}` const hash = murmurhash.v3(hashInput)</p><p>// Compute bucket number between 1 and 100 const bucket = (hash % 100) + 1 // Adjust to 1-100 range</p><p>return bucket ; }</p><p>const variant = getABTestVariant(userId, 'FF_NEW_EXPERIENCE');</p><p>return variant === 'A' ?  : ; } ```</p><p>In this example:</p><p>- **Role-Based Access**: Users with specific roles can access the feature immediately. - **Percentage Rollout**: A certain percentage of users are granted access. - **A/B Testing**: Users with access are further split into variants 'A' and 'B' for testing purposes.</p><p>---</p><p>## Limitations and Considerations</p><p>### Environment Variables and Build Time in Next.js</p><p>**Client-Side Limitations:**</p><p>- **Static Injection at Build Time**: In Next.js, environment variables prefixed with `NEXT_PUBLIC_` are statically injected into your client-side code at build time. This means that any changes to these variables require you to rebuild and redeploy your application. - **Limited Dynamic Toggling**: Due to static injection, you cannot dynamically toggle features on the client side without redeployment. If real-time toggling is essential, consider using an API call or real-time configuration service to fetch feature flags at runtime.</p><p>### Alternatives for Dynamic Client-Side Feature Toggling</p><p>- **API Calls**: Fetch feature flags from a server or API endpoint at runtime. - **Real-Time Configuration Services**: Use services like Firebase Remote Config or LaunchDarkly for dynamic feature management.</p><p>---</p><p>## Security Considerations</p><p>- **Avoid Exposing Sensitive Flags**: Use prefixes like `NEXT_PUBLIC_` cautiously to prevent leaking sensitive data. - **Secure Environment Variables**: Keep sensitive variables on the server side and do not expose them to the client. - **Version Control Practices**: Exclude `.env` files from version control to protect secrets. - **User Role Verification**: Ensure that user roles are securely verified and cannot be tampered with on the client side.</p><p>---</p><p>## Best Practices</p><p>- **Incorporate `featureName` in Hash Functions**: Always include the `featureName` when hashing to assign users to buckets. This ensures fair and independent distribution across different features. - **Use Secure Methods to Determine User Roles**: Fetch user roles from a secure source (e.g., server-side authentication) rather than trusting client-provided data. - **Logging and Monitoring**: Implement logging to track feature flag usage and changes. - **Graceful Fallbacks**: Ensure your application behaves correctly if feature flags are misconfigured. - **Testing**: Rigorously test feature flag logic to prevent unauthorized access or feature exposure. - **Documentation**: Keep clear documentation of your feature flags and their configurations to aid in maintenance and onboarding.</p><p>---</p><p>## Final Thoughts</p><p>Environment variable-driven feature flags provide a robust, scalable, and cost-effective way to manage features across different environments. By starting with simple flags and extending to advanced use cases like role-based access, A/B testing, and percentage rollouts—with the critical step of incorporating the `featureName` into your hash functions—you can:</p><p>- **Enhance Deployment Flexibility**: Confidently deploy features across various user segments. - **Improve User Experience**: Tailor features to specific user groups. - **Reduce Costs**: Eliminate the need for external feature flagging services.</p><p>However, it's crucial to understand the limitations of environment variables, especially in Next.js. Since client-side environment variables are injected at build time, dynamic toggling on the client without redeployment isn't feasible. For real-time feature management on the client side, consider integrating an API or a real-time configuration service.</p><p>By integrating the user role-based feature flag mechanism, you can control feature access based on user permissions, roles, or subscription levels. Combining this with percentage-based rollouts and A/B testing allows for highly granular control over how and to whom features are deployed.</p><p>---</p><p>Cheers, **Tómas**</p><p>---</p><p>**Note:** This guide demonstrates how to implement feature flags effectively in both client and server components by using context providers and custom hooks in client components and either wrapper components or direct methods in server components. Adjust the implementation details according to your specific framework and project requirements.</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
        <item>
            <title><![CDATA[Set Up Next.js Favicons with Just 3 Files]]></title>
            <link>https://theodortomas.com/articles/favicons-made-simple</link>
            <guid>https://theodortomas.com/articles/favicons-made-simple</guid>
            <pubDate>Sat, 23 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to set up favicons in Next.js using only three essential files for a quick and efficient configuration.]]></description>
            <content:encoded><![CDATA[<p>## TL;DR</p><p>### **Essential Favicons Setup**</p><p>For a quick and efficient favicon setup in Next.js, you only need these three files:</p><p>1. **favicon.ico**</p><p>- **Purpose**: The classic favicon displayed in browser tabs. - **Placement**: Place a 32×32 `favicon.ico` file in the `app` directory. Next.js will automatically serve it at `/favicon.ico`.</p><p>2. **icon.svg**</p><p>- **Purpose**: Modern browsers prefer SVG icons for scalability and crispness. - **Placement**: Save your SVG icon as `icon.svg` in the `public` directory. - **Usage in Next.js Metadata**:</p><p>```javascript { rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', }</p><p>```</p><p>3. **apple-touch-icon**</p><p>- **Purpose**: Used when users add your website to their iOS home screen. - **Placement**: Add a 180×180 PNG named `apple-touch-icon.png` to the `public` directory. - **Usage in Next.js Metadata**:</p><p>```javascript { rel: 'apple-touch-icon', sizes: '180x180', url: '/apple-touch-icon.png', } ```</p><p>**Note**:</p><p>- The `app` directory in Next.js 13 (using the App Router) is special because placing a `favicon.ico` file at the root of this directory ensures it's automatically served at `/favicon.ico` without additional configuration. - The `public` directory is ideal for other static assets because it's served at the root (`/`) of your application, making it straightforward to reference these files in your code.</p><p>This minimal setup ensures compatibility with most browsers and devices, providing essential icon support without unnecessary complexity.</p><p>---</p><p>## Expanded Setup for PWA Support</p><p>If your app is a Progressive Web App (PWA), you'll need to define additional icons in a `manifest.json` file. This enhances the user experience when your app is installed on a device.</p><p>### Additional Icons for PWAs</p><p>1. **192×192 PNG**</p><p>- **Purpose**: Used for Android home screen icons and in notifications. - **Placement**: Save as `icon-192.png` in the `public` directory.</p><p>2. **512×512 PNG**</p><p>- **Purpose**: The default icon for splash screens when the app is launched. - **Placement**: Save as `icon-512.png` in the `public` directory.</p><p>3. **512×512 Maskable PNG**</p><p>- **Purpose**: Ensures proper display on devices that apply masks to icons (e.g., Android devices with non-rectangular shapes). - **Placement**: Save as `icon-maskable.png` in the `public` directory. - **Design Consideration**: Include extra padding. The safe area for a maskable icon is a central circle of 409×409 pixels inside the 512×512 canvas. Use tools like <a href="https://maskable.app">maskable.app</a> to adjust the padding correctly.</p><p>### Example Manifest File</p><p>Create a `manifest.json` file in the `public` directory:</p><p>```json { "name": "Your App Name", "short_name": "App", "start_url": ".", "display": "standalone", "background_color": "#FFFFFF", "description": "Your app description", "icons": [ { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/icon-maskable.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" }, { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } ] } ```</p><p>---</p><p>## HTML Reference for Non-Next.js Apps</p><p>If you're not using Next.js, include the following in your ``:</p><p>```html</p><p>```</p><p>### Generating the favicon.ico File</p><p>To generate the `.ico` file with 128x128, 64x64, 48x48, 32×32 and 16×16 resolutions, use ImageMagick:</p><p>** Note use a 1200x1200 image for best results **</p><p>```bash magick source.png -resize 16x16 icon-16.png magick source.png -resize 32x32 icon-32.png magick source.png -resize 48x48 icon-48.png magick source.png -resize 64x64 icon-64.png magick source.png -resize 128x128 icon-128.png ```</p><p>Combine them into a single favicon.ico file.</p><p>```bash magick icon-128.png icon-64.png icon-48.png icon-32.png icon-16.png favicon.ico ```</p><p>Place the generated `favicon.ico` in your application's root directory.</p><p>---</p><p>## Optional: Theming with Media Queries</p><p>For advanced theming, you can define light and dark mode icons using media queries in the `metadata.icons` array:</p><p>```javascript export const metadata = { icons: [ { rel: 'icon', media: '(prefers-color-scheme: light)', url: '/icon-light.svg', type: 'image/svg+xml', }, { rel: 'icon', media: '(prefers-color-scheme: dark)', url: '/icon-dark.svg', type: 'image/svg+xml', }, ], } ```</p><p>This setup automatically displays the appropriate icon based on the user's system settings, enhancing user experience by aligning with their preferred theme.</p><p>---</p><p>## Understanding the Purpose of Each Icon</p><p>### favicon.ico</p><p>- **Purpose**: The default icon displayed in browser tabs and bookmarks. - **Why It's Needed**: Provides brand recognition and improves user experience by allowing users to quickly identify your site among multiple tabs. - **Placement in Next.js**: Place it in the `app` directory root. Next.js will automatically serve it at `/favicon.ico`.</p><p>### icon.svg</p><p>- **Purpose**: A scalable icon for modern browsers, ensuring crisp display on all devices. - **Why It's Needed**: SVGs scale without loss of quality, making them ideal for high-resolution displays. - **Placement**: Place in the `public` directory.</p><p>### apple-touch-icon.png</p><p>- **Purpose**: Used when iOS users add your website to their home screen. - **Why It's Needed**: Ensures your app icon looks professional and recognizable on iOS devices. - **Placement**: Place in the `public` directory.</p><p>---</p><p>## SEO and Accessibility Considerations</p><p>- **Brand Recognition**: Consistent and professional icons improve brand visibility across devices and platforms. - **User Experience**: Properly sized and formatted icons ensure a seamless experience, whether users are browsing, bookmarking, or adding your app to their home screen. - **Accessibility**: High-quality icons with appropriate sizes enhance readability and usability for all users.</p><p>---</p><p>## Troubleshooting Tips</p><p>- **Browser Caching**: Browsers often cache favicons and may not immediately reflect updates.</p><p>**Solution**: Instruct users (or yourself during testing) to clear the browser cache or perform a hard refresh (usually `Ctrl+F5` or `Cmd+Shift+R`).</p><p>- **Incorrect File Paths**: Ensure that your icon files are placed in the correct directories and that the paths in your code match.</p><p>- **Missing Sizes or Types**: Omitting `sizes` or `type` attributes can lead to browsers ignoring your icons.</p><p>**Solution**: Always specify the `sizes` and `type` attributes in your metadata or HTML tags.</p><p>---</p><p>## Additional Resources</p><p>- **Next.js Documentation on Metadata**: <a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata">Next.js Metadata API</a> - **Favicon Generator and Checker**: <a href="https://realfavicongenerator.net/">RealFaviconGenerator.net</a> - **PWA Icon Guidelines**: <a href="https://web.dev/add-manifest/#icons">Web.dev - Create app icons for your PWA</a> - **Apple Touch Icon Guide**: <a href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/">Apple Human Interface Guidelines</a></p><p>---</p><p>## Recap: All the Icons You Need</p><p>Here's a summary of all the icons and their configurations for a fully functional setup:</p><p>### Files to Include</p><p>- **favicon.ico**: 32×32 (placed in `app/` directory) - **icon.svg**: Scalable SVG icon (placed in `public/` directory) - **apple-touch-icon.png**: 180×180 PNG (placed in `public/` directory) - **icon-192.png**: 192×192 PNG (for Android home screens) - **icon-512.png**: 512×512 PNG (for splash screens) - **icon-maskable.png**: 512×512 PNG with padding (for maskable icons)</p><p>### Next.js Metadata Example</p><p>```javascript { "icons": [ { rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', }, { rel: 'apple-touch-icon', sizes: '180x180', url: '/apple-touch-icon.png', }, // Only required for PWA apps. { rel: 'manifest', url: '/manifest.json', } ] } ```</p><p>---</p><p>## A Simple Favicon Solution for MVPs</p><p>If you’re building a Minimum Viable Product (MVP) or want a quick solution without worrying about multiple icon sizes, use Next.js’s built-in `ImageResponse` to generate a simple 32×32 favicon dynamically:</p><p>```jsx export const contentType = 'image/png'</p><p>export default function Icon() { return new ImageResponse( (  MVP  ), { width: 32, height: 32 }, ) } ```</p><p>### Why This Works</p><p>- **Dynamic Generation**: The favicon is created on the fly. - **Simple Setup**: Great for MVPs or quick projects that don’t require complex configurations. - **Good Enough for Most Scenarios**: Works well for browser tabs but may not suit PWAs or iOS home screen icons.</p><p>Start with this approach and upgrade to a more comprehensive setup as your project grows.</p><p>---</p><p>## Conclusion</p><p>Setting up favicons in Next.js doesn’t have to be complicated. With just three files, you can ensure your app looks polished and professional across browsers and devices. For more advanced setups, adding PWA support and media queries enhances the user experience even further.</p><p>Cheers, **Tómas**</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
        <item>
            <title><![CDATA[Hello World!]]></title>
            <link>https://theodortomas.com/articles/hello-world</link>
            <guid>https://theodortomas.com/articles/hello-world</guid>
            <pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Welcome to my little corner of the internet! This blog will explore the adventures of being a solo entrepreneur, digital nomad, full-time Lead Software Engineer, and owner of Three Tech Consulting.]]></description>
            <content:encoded><![CDATA[<p>Welcome to **theodortomas.com**! This is where I’ll be sharing insights, stories, and lessons from my journey as a solo entrepreneur, digital nomad, and full-time Lead Engineer. Oh, and did I mention I’m also the owner of **Three Tech Consulting**? Balancing travel, running my own business, and leading engineering teams has been a wild ride, and this blog is here to document it all.</p><p>As the owner of **Three Tech Consulting**, I’ve worked with mid-to-large companies serving millions of monthly users to deliver cutting-edge software solutions. At the same time, as a Lead Engineer, I’ve led teams to build scalable applications, tackle complex challenges, and deliver impactful results—all while juggling personal side projects along the way.</p><p>This blog isn’t just about the work. It’s also about the journey—how to balance a career in tech with exploring the world. Being a **digital nomad** means typing away in cafes, chasing stable Wi-Fi, and navigating the highs and lows of building a career on the move. Whether it’s brainstorming product ideas on a beach in Bali or squashing bugs on a train in Europe, I’ve got stories to share.</p><p>## What to Expect</p><p>Here’s what you can look forward to:</p><p>- **Entrepreneurship**: Thoughts on starting, scaling, and balancing the chaos of running your own business. - **Web Development**: Tips, tricks, and deep dives into building better, faster, and more scalable applications. - **Nomad Life**: Honest takes on the highs and lows of working while traveling. - **Balancing It All**: How I juggle a full-time Lead Engineer role, running **Three Tech Consulting**, and living the nomadic life. - **Everything Else**: Random musings, tech inspiration, and maybe even a few of my favorite chess opening moves—like the Sicilian Defense or the Ruy-Lopez.</p><p>This blog is for anyone curious about blending passion, work, and travel—or for those just looking for a glimpse into the life of someone trying to do it all.</p><p>Whether you’re here for entrepreneurial inspiration, tech insights, or nomadic adventures, I hope you find something worth your time.</p><p>Cheers, **Tómas**</p>]]></content:encoded>
            <author>theodortomas@threetech.consulting (Theodór Tómas)</author>
        </item>
    </channel>
</rss>