“BÜYÜK ŞEYTAN” BELGESELİ – Yapay Zeka Savaşa Nasıl Katıldı?

Yapay zeka artık sadece bir sohbet botu değil, Mart 2026 itibarıyla savaş meydanlarında doğrudan bir “infaz memuru” olarak görevde. İnsanlığın ufkunu açması için geliştirilen bu dehanın, nasıl soğukkanlı bir katile dönüştüğüne şahitlik ediyoruz. Peki, her gün kullandığımız ChatGPT (OpenAI), Claude (Anthropic), Gemini (Google) ve Grok (xAI) gibi devler bu karanlık denklemin neresinde?

Amazon’dan bahara özel kampanya; işte detaylar

Amazon

AmazonAmazon, baharın gelişine özel bir kampanya başlattı. 18 Mart 23:59‘a kadar sürecek ”Bahar Fırsatları” kampanyasında  robot süpürgelerden kablosuz kulaklıklara, mutfak ürünlerinden akıllı ev cihazlarına kadar pek çok teknoloji ürünü avantajlı fiyatlarla sunuluyor. Kampanya ayrıca seçili ürünlerde 9 aya varan taksit imkanı sağlarken, kullanıcılar yapay zeka destekli Amazon Lens özelliği sayesinde aradıkları ürünleri fotoğraf, görsel veya barkod ile hızlıca bulup satın alabiliyor. Amazon.com.tr’nin 18 Mart’a kadar devam edecek “Bahar Fırsatları” kampanyası da tam bu yenilenme döneminde ihtiyaç duyulabilecek tüm teknoloji ürünleri cazip fırsatlarla sunuyor. Kampanya süresince müşteriler; modadan, kişisel bakıma, spordan gıda ürünlerine kadar pek çok kategoride olduğu gibi elektronik ve teknoloji kategorilerindeki ürünlere de avantajlı fiyatlarla sahip olabiliyor. Kampanya kapsamında; ev temizliğini kolaylaştıran akıllı robot süpürgeler ve kablosuz dikey süpürgeler, yemek yapmayı kolaylaştıran mutfak ürünleri, temizlik yaparken müzik dinleyebileceğiniz kablosuz kulaklıklar, ev düzenini daha akıllı hale getiren akıllı ev ürünleri, iftar ve sahur tariflerine hızlıca ulaşmayı sağlayan tabletler ve günlük yaşamın […]

Amazon’dan bahara özel kampanya; işte detaylar yazısı ilk olarak Teknoloji haberleri – LOG üzerinde yayınlandı.

Building a Seamless JWT Onboarding Flow with React Router v7 and Django

Authentication and onboarding are often the highest-friction points in a new user’s journey. If the process is clunky or requires too many redirects, users drop off. Recently, I set out to build a streamlined JWT authentication and onboarding flow using a modern stack: React Router v7 (with Vite), TypeScript, and Django REST Framework (DRF).

In this article, I’ll walk through the architecture, the backend implementation, and how to handle protected routes securely on the client side.

The Architecture Stack

Here is what we are working with:

  • Backend: Django, Django REST Framework (DRF), and djangorestframework-simplejwt.
  • Frontend: React (via Vite) and the newly released React Router v7.
  • Styling: Tailwind CSS.
  • State Management: React Context API for lightweight auth state handling.

The Goal

  1. A user registers.
  2. The user logs in. They receive a short-lived access token and a refresh token.
  3. The backend knows if the user has completed their profile setup (is_onboarded).
  4. If they haven’t onboarded, the backend middleware blocks API requests, and the frontend automatically reroutes them to the /onboarding page.
  5. Once onboarded, new tokens are issued with the updated status, and the user gains full access to the application /.

1. Setting up the Django Backend

The Custom User Model

First, we need to track whether a user has finished setting up their profile. We extend Django’s AbstractUser:

# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    is_onboarded = models.BooleanField(default=False)

Customizing the JWT Payload

By default, SimpleJWT only includes the user_id inside the token. We want the frontend to immediately know the user’s name and onboarding status without making an extra API call.

# users/serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # Inject custom claims into the JWT payload
        token['is_onboarded'] = user.is_onboarded
        token['username'] = user.username
        return token

Enforcing Onboarding with Middleware

We want to strictly prevent un-onboarded users from accessing core application data. We do this via a custom Django middleware that intercepts requests.

# users/middleware.py
from django.http import JsonResponse
from django.urls import reverse

class OnboardingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Exempt authentication and onboarding endpoints
        exempt_urls = [reverse('token_obtain_pair'), '/api/onboard/submit/', '/api/register/']

        if request.path in exempt_urls or request.path.startswith('/admin/'):
            return self.get_response(request)

        user = request.user
        # Block access if authenticated but not onboarded
        if user.is_authenticated and not user.is_onboarded:
            return JsonResponse({
                "error": "ONBOARDING_REQUIRED",
                "message": "Please complete onboarding before accessing this resource."
            }, status=403)

        return self.get_response(request)

Now, even if a user tries to bypass the frontend routing, the API will refuse to serve them data!

Refreshing Tokens upon Onboarding

When the user submits the onboarding form, their status changes in the database. However, their physical JWT won’t update automatically. We handle this by issuing completely new tokens in the onboarding response:

# users/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions

class OnboardSubmitView(APIView):
    permission_classes = (permissions.IsAuthenticated,)

    def post(self, request):
        user = request.user
        if user.is_onboarded:
            return Response({"detail": "Already onboarded."}, status=400)

        user.is_onboarded = True
        user.save()

        # Generate fresh tokens reflecting the new state
        refresh = MyTokenObtainPairSerializer.get_token(user)

        return Response({
            "detail": "Onboarding complete.", 
            "is_onboarded": True,
            "access": str(refresh.access_token),
            "refresh": str(refresh)
        }, status=status.HTTP_200_OK)

2. Setting up the React Frontend

We spun up the frontend using React Router v7’s built-in Vite template.

Bypassing CORS locally

To avoid CORS issues during development, we configured Vite to proxy /api requests to our Django dev server:

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  // plugins...
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      }
    }
  }
});

Global Authentication Context

We use React Context to provide user data (token, user payload, login, logout) globally.

A critical detail: Server-Side Rendering (SSR). React Router v7 utilizes SSR by default when hydrating the app. If you try to read localStorage.getItem('access_token') immediately on the server, Node.js will crash with a ReferenceError: localStorage is not defined.

We fix this by waiting for hydration on the client using a simple useEffect:

// app/contexts/AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';

// ... interfaces and parseJwt helper ...

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [token, setToken] = useState<string | null>(null);
  const [isClient, setIsClient] = useState(false);

  // Safe SSR hydration
  useEffect(() => {
    setIsClient(true);
    setToken(typeof window !== "undefined" ? localStorage.getItem('access_token') : null);
  }, []);

  const user = token ? parseJwt(token) as UserPayload : null;

  useEffect(() => {
    if (token) localStorage.setItem('access_token', token);
    else localStorage.removeItem('access_token');
  }, [token]);

  const login = (access: string, refresh: string) => {
    localStorage.setItem('refresh_token', refresh);
    setToken(access);
  };

  if (!isClient) return null; // Prevent hydration mismatch

  return (
    <AuthContext.Provider value={{ token, user, login, logout, refreshAccessToken }}>
      {children}
    </AuthContext.Provider>
  );
}

Route Protection and Redirection

With our context in place, protecting routes and enforcing onboarding becomes incredibly simple. We intercept users in a useEffect on our page components.

Here is what our protected Home Dashboard looks like:

// app/routes/home.tsx
import { useEffect } from "react";
import { useNavigate } from "react-router";
import { useAuth } from "../contexts/AuthContext";

export default function Home() {
  const { token, user, logout } = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    if (!token) {
      navigate("/login");               // Kick out unauthenticated users
    } else if (user && !user.is_onboarded) {
      navigate("/onboarding");          // Kick out un-onboarded users
    }
  }, [token, user, navigate]);

  // Don't render until verified to prevent brief flashes of content
  if (!user || !user.is_onboarded) return null;

  return (
    <div>
      <h1>Welcome to your portal, {user.username}!</h1>
    </div>
  );
}

When the user is pushed to /onboarding, they hit our submit button. The API returns the new tokens, we update the context, and React Router instantly pushes them back to / seamlessly!

// Inside Onboarding component submit handler
const handleComplete = async () => {
  const res = await fetch("/api/onboard/submit/", {
    method: "POST",
    headers: { "Authorization": `Bearer ${token}` }
  });
  if (res.ok) {
    const data = await res.json();
    login(data.access, data.refresh); // Instantly updates global context
    navigate("/");                    // Redirect to dashboard
  }
};

Conclusion

By embedding the is_onboarded claim directly inside the JWT, we eliminate the need for the frontend to constantly ping the database to check a user’s status. Coupling this tightly with Django Middleware ensures backend security, while utilizing React Context provides snappy, seamless redirects on the frontend.

Building auth doesn’t have to be painful—with the right architecture, it can be safe, scalable, and a great experience for the end user.

50 Hours Building a Next.js Boilerplate So You Can Ship in 30 Minutes!

Leading Next.js Boilerplate Screenshot

Next.js Boilerplate: The Ultimate SaaS Starter Kit

Looking for the best Next.js Boilerplate to launch your next project? You’ve found it. This production-ready starter kit is designed to help you go from idea to deployment in record time.

Table of Contents

  1. The Problem That Kept Me Up at Night
  2. Why This Next.js Boilerplate is Different
  3. Key Features of Nextjs-Elite-Boilerplate
  4. How to Get Started (The Real Way)
  5. The Project Structure (Explained for Humans)
  6. What You Get Out of the Box
  7. Contributing & Support
  8. Final Thoughts

The Problem That Kept Me Up at Night

You know that feeling when you start a new Next.js project and spend the first week just setting things up? Authentication, internationalization, role management, SEO configuration… By the time you’re done with the boilerplate, you’ve lost all that initial excitement.

What you get in one line: A production-ready Next.js SaaS Boilerplate with Type-safe i18n (6 languages) + NextAuth, Google OAuth + RBAC with parallel routes + SEO (sitemap, robots, manifest) + Theme + ESLint + Prettier + Vitest + Playwright.

I’ve been there. Too many times. After building multiple SaaS projects, I realized I was copy-pasting the same setup code. So I built the ultimate Next.js starter kit.

Why This Next.js Boilerplate is Different

Meet Nextjs-Elite-Boilerplate: not just another “hello world” starter template, but a fully-featured foundation that handles all the boring stuff so you can focus on building your actual product.

Best part? I’ve made this project completely open source for everyone. Whether you’re a student learning the ropes or a seasoned dev looking for a solid starting point, this is for you.

Important Links:

🔗 Live Demo | 📦 GitHub Repo | 🚀 Use this template | Deploy on Vercel

Leading Next.js Boilerplate Screenshot

Key Features of Nextjs-Elite-Boilerplate

🌍 Real Internationalization (i18n) for Global SaaS

  • Six languages out of the box: English, বাংলা, العربية, Français, Español, and 简体中文.
  • RTL support that actually works: Arabic layouts automatically flip to right-to-left.
  • Next.js 16 Ready: Fully optimized for the latest React features.

🔐 Role-Based Access Control (RBAC) That Scales

I used Next.js 16’s parallel routes to make this painless:

src/app/
  (protected)/
    @admin/      # Admin-only views
      dashboard/
    @user/       # User views
      dashboard/
    layout.tsx   # Smart routing logic

The layout automatically shows the right dashboard based on the user’s role. No messy conditionals scattered everywhere. Want to add a new role? Just create a new parallel route folder. That’s it.

🎨 UI/UX with shadcn/ui

  • Components are copy-paste ready (no bloated dependencies).
  • Full TypeScript support & accessible by default.
  • Built-in Dark Mode with next-themes.

🔧 ESLint That Actually Helps (Not Annoys)

  • Catch real bugs (unused variables, missing dependencies, potential null references)
  • Enforce consistency (import order, naming conventions, formatting)
  • eslint-config-next – Official Next.js rules
  • TypeScript-specific linting

Prettier is wired up too. Run npm run lint and npm run prettier:fix for consistent, clean code.

📊 SEO Configuration That’s Actually Usable

I created a single JSON configuration file that handles:

  • Open Graph tags
  • Structured data (JSON-LD)
  • Multi-language meta tags
  • Canonical URLs
  • Dynamic sitemap generation

Just edit one file:

{
  "appName": "Your App",
  "title": "Your Title",
  "description": "Your Description",
  "domain": "https://yoursite.com",
  "keywords": ["your", "keywords"],
  "social": {
    "twitter": "@yourhandle"
  }
}

🧪 Testing & CI

  • Unit and component tests: Vitest + React Testing Library. Run npm run test or npm run test:watch; npm run test:coverage for coverage.
  • E2E: Playwright in e2e/. Run npm run e2e (dev server starts automatically); npm run e2e:ui for the UI. Use npm run e2e:webkit for WebKit-only (e.g. to save disk).
  • CI: GitHub Actions – .github/workflows/check.yml runs lint, Prettier, tests, and build on push/PR; .github/workflows/playwright.yml runs E2E.

🔑 Authentication (NextAuth.js + Google OAuth)

  • Google OAuth – enable by setting GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true in .env
  • Custom login page at /auth/login with optional “Sign in with Google”
  • JWT session with role and user id; redirect to /dashboard after sign-in

Copy .env.example to .env, add your secrets, and you’re done.

🏥 Health Check

GET /api/health returns { status: "ok" } for load balancers and Kubernetes probes.

How to Get Started (The Real Way)

Step 1: Clone and Install

# Grab the code
git clone https://github.com/salmanshahriar/Nextjs-Elite-Boilerplate.git
cd Nextjs-Elite-Boilerplate

# Install dependencies (use whatever you prefer)
npm install
# or bun install / yarn install / pnpm install

Step 2: Configure Your Project

This is where most boilerplates leave you hanging. Not this one.

Edit src/shared/lib/config/app-main-meta-data.json:

{
  "appName": "My Awesome SaaS",
  "title": "Revolutionary Product That Does X",
  "description": "We help Y achieve Z",
  "domain": "https://myawesomesaas.com",

  "organization": {
    "name": "My Company",
    "email": "[email protected]"
  },

  "social": {
    "twitter": "@myhandle",
    "github": "https://github.com/myhandle"
  }
}

That’s your entire brand configuration. One file.

Step 3: Customize Your Languages (Optional)

Want to add Spanish? Here’s how:

  1. Create locales/es.json with the same structure as locales/en.json:
{
  "common": { "appName": "Mi App", ... },
  "navigation": {
    "home": "Inicio",
    "about": "Acerca de"
  }
}
  1. Update src/shared/lib/config/app-main-meta-data.json:
{
  "languages": {
    "supported": ["en", "bn", "ar", "es"],
    "locales": {
      "es": {
        "code": "es",
        "name": "Spanish",
        "nativeName": "Español",
        "locale": "es_ES",
        "direction": "ltr"
      }
    }
  }
}
  1. In src/shared/lib/i18n/get-translations.ts, import es.json and add es to the translations object. If you use strict translation keys, add the new locale to the TranslationKeys union in src/shared/lib/i18n/types.ts.

Done. Your app now speaks Spanish.

Step 4: Set Up Your Roles

The boilerplate comes with User and Admin roles. To add more:

  1. Create a new parallel route folder:
mkdir -p src/app/(protected)/@moderator/dashboard
  1. Add your pages inside that folder

  2. Update src/app/(protected)/layout.tsx to handle the new role:

if (currentUser?.role === 'moderator') return moderator

That’s genuinely all you need to do.

Step 5: Run It

npm run dev

Open http://localhost:3000 and see your fully-configured app running.

Available Scripts

Command Description
npm run dev Start development server
npm run build Production build
npm run start Start production server
npm run lint Run ESLint
npm run lint:fix Fix ESLint errors
npm run test Unit tests (Vitest)
npm run test:watch Unit tests in watch mode
npm run test:coverage Tests with coverage
npm run e2e Playwright E2E tests
npm run e2e:ui Playwright with UI
npm run e2e:webkit E2E in WebKit only
npm run prettier Check formatting
npm run prettier:fix Fix formatting

Step 6: Environment (First-Time Setup)

Copy .env.example to .env. Set NEXT_PUBLIC_APP_URL if you need to override the site URL (e.g. in production). For Google sign-in: set NEXTAUTH_URL, NEXTAUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, then NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true. Optionally set [email protected] so those emails get the admin role.

Prerequisites

  • Node.js 18.17 or later
  • npm, yarn, pnpm, or bun

The Project Structure (Explained for Humans)

src/
  app/
    (protected)/         # Routes behind auth (@admin, @user)
    (public)/            # Public routes (Home, About)
    auth/                # Auth-related pages (Login)
    api/                 # API routes (Auth, Health)
    layout.tsx           # Global layout & providers
    page.tsx             # Landing page
    ...
  shared/
    ui/                  # shadcn/ui components
    layout/              # Header, Sidebar, ThemeToggle
    components/          # Feature components
    hooks/               # Reusable hooks
    lib/                 # i18n, auth config, site metadata
locales/                 # i18n JSON files (en, bn, ar, fr, es, zh)
e2e/                     # Playwright E2E tests
.github/workflows/       # CI/CD pipelines

What You Get Out of the Box

✅ Next.js 16 with App Router and Server Components

✅ TypeScript (strict mode)

✅ Tailwind CSS

✅ ESLint and Prettier (Next.js, TypeScript, a11y, format on save in .vscode)

✅ NextAuth.js with optional Google OAuth and admin-by-email

✅ i18n with type safety and RTL (en, bn, ar, fr, es, zh)

✅ RBAC with parallel routes (User / Admin)

✅ SEO from one JSON config (metadata, sitemap, robots, manifest)

✅ next-themes for dark mode (system + manual toggle)

✅ shadcn/ui (accessible, customizable)

✅ Vitest + React Testing Library for unit/component tests

✅ Playwright for E2E in e2e/

✅ GitHub Actions for lint, format, test, build and E2E

✅ Health check at GET /api/health

✅ Vercel-ready (one-click deploy from the repo)

Real Talk: When Should You Use This?

Perfect for:

  • SaaS products with multiple user types
  • International applications
  • Projects where you want to ship fast

Maybe not ideal for:

  • Projects with very specific auth requirements (you’ll need to customize heavily)
  • Apps that don’t need i18n or role management

Contributing & Support

Found a bug? Want to add a feature? The repo is fully open source:

🐛 Report issues

⭐ Star on GitHub

🤝 Submit a PR

Final Thoughts

I built this because I got tired of setting up the same infrastructure for every project. Now that it’s open source, I hope it helps you ship your next big idea even faster.

What’s your biggest pain point when starting a new Next.js project?
Drop a comment below; I’m always looking to improve this.

Happy building! 🚀

Important Links:

🔗 Live Demo | 📦 GitHub Repo | 🚀 Use this template | Deploy on Vercel

Learning to code

The Creator Void: My Journey from Machines to Code

I have always had an inner urge from my childhood; I have been a curious kid from early. I vividly remember figuring things out on my own—how phones work, how machines are built. Every opportunity to disassemble a machine was a golden chance. I questioned things, I tried things, and I failed at many, of course. But importantly, I learned how things work, and that has always been a life thread for me.

I think of myself as intellectually thirsty, with a “creator void” inside me. I’m not satisfied with mere attainments. However, there have been sidetracks and “sideshows,” as my dad would call them, that have kept me from reaching the ultimate version of me—but today, that would not be our topic.

The Barrier of the “Qualified IT Guy”

Growing up in my teens, my passion with gadgets did not diminish but became augmented. I started opening up CPUs, disassembling the CMOS and the hard drives, reassembling them, and installing operating systems. But I dreaded one thing: writing lines of code. I never thought I was worthy or qualified. I always looked to a point in the future when my brain would be able to comprehend such things.

Fast forward to my campus days doing my Bachelor’s in Business and IT. I had started out hot, but again, to use my dad’s words, I became sidetracked. I abandoned the journey for a long time—so long that I graduated without being a “qualified IT guy.” But life granted me a chance once again to rediscover the ultimate me. For the past two months, I have written more lines of code than I have written my entire life, and there is something I’m learning from all this that I hope helps software engineer aspirants.

1. The House and the Foundation

Just as building a house requires a solid foundation, a software engineer needs to be grounded on elementary principles of writing code. Learn how data is manipulated at the very core—strings manipulation, bytes, bits, ASCII. Learn the foundational blocks and know them very well. It can take up to a month of consistency just practicing the basics, but later, as you start working on projects like building simple tools, you will realize that you are just applying fundamental blocks but now compiling them.

Think of it like building a kitchen: you will use many different blocks, but you design it in a way that serves the purpose of cooking. I insist highly on being grounded on the building blocks.

2. The Freedom of the Disciplined

Secondly, to code requires a high frequency of consistency. You must make sure that you practice almost daily; you make it a part of your life. There is no other way to scale up. You must be consistent and disciplined. As the great Eliud Kipchoge would say: “Only the disciplined ones are free in life. If you are undisciplined, you are a slave to your moods and your passions.” You might be very talented, but all that talent will avail to nothing if you are not disciplined in life. As it is written in Ecclesiastes 9:11: “I returned, and saw under the sun, that the race is not to the swift, nor the battle to the strong, neither yet bread to the wise, nor yet riches to men of understanding, nor yet favor to men of skill; but time and chance happens to them all.”

3. The Magic of Peer-to-Peer Learning

Thirdly, I would highly recommend peer-to-peer learning. It’s good to spend time alone trying to figure things out, but it should not end at that. There is a magic that sparks when you unite with like-minded peers. Sharing your ideas makes you understand them better.

Most importantly, you get to see things from a different light and a different perspective when others share their ideas and approach to problem-solving. Your understanding broadens. There is always a “good leaf” to borrow from others. As Charles Hazlitt Puffer wrote: “He who does not read the thoughts of others will never have any of his own.” It is just as it is put in Proverbs 27:17: “Iron sharpens iron; so a man sharpens the countenance of his friend.”

well, the journey has not been straight, there have always been crooked and thorny paths and to you, i do not promise a smooth journey, but a sure arrival.

Onelingo MVP: Lo que aprendí cuando mi app de idiomas no salió como esperaba (Post-Mortem)

Onelingo MVP: Lo que aprendí cuando mi app de idiomas no salió como esperaba

¡Hola a todos! Hoy no vengo a traerles el típico tutorial donde todo sale perfecto a la primera. Vengo a contarles la historia de Onelingo, una app de micro-desafíos diarios de idiomas que intenté construir usando Hugging Face Spaces.

Aunque el resultado final no fue exactamente lo que visualicé inicialmente, el proceso me dejó lecciones invaluables sobre despliegue, autenticación y la importancia de una arquitectura limpia. Aquí les comparto mi “Post-Mortem” técnico.

1. El concepto: Onelingo

La idea era crear una alternativa a Duolingo pero sin niveles infinitos: solo 3 micro-desafíos diarios (Reading, Listening y Speaking).

El stack elegido:

  • Hosting: Hugging Face Spaces (Static SDK).
  • IA de apoyo: HuggingChat (Llama 3 / Qwen).
  • Frontend: HTML/JS/CSS con Tailwind vía CDN.

2. El primer obstáculo: El “Rito de Iniciación” de Git

Al intentar mover el código desde el editor web de Hugging Face a mi entorno local (WebStorm), me topé con el error que detiene a muchos principiantes:
remote: Invalid username or password. fatal: Authentication failed.

El aprendizaje:

Hugging Face no usa tu contraseña de usuario para las operaciones de Git. Necesitas un Access Token.

La solución técnica:

Si te sucede esto, debes forzar la URL del remoto para incluir tu token:

git remote set-url origin https://TU_USUARIO:[email protected]/spaces/TU_USUARIO/onelingo-mvp

Tip: Genera tu token en Settings > Access Tokens con permisos de “Write”.

3. El error de lógica: El “Esqueleto” sin cerebro

inicialmente, la app se veía genial con Tailwind, pero al hacer clic en las opciones de respuesta, no pasaba nada. El código generado por la IA era visualmente correcto pero funcionalmente vacío.

El Aprendizaje:

No confíes ciegamente en el primer bloque de código que te da una IA. Siempre verifica que las funciones de validación de estado (como el manejo de respuestas correctas/incorrectas) estén implementadas.

La solución futura:

Para evitar esto, mi recomendación es separar la lógica en archivos distintos desde el principio:

  • index.html: Estructura.
  • style.css: Diseño.
  • app.js: El “cerebro” (validación de desafíos y Web Speech API).

4. Implementando speaking: El reto del Navegador

Intentamos usar la Web Speech API para el reconocimiento de voz. Aunque funciona bien en Chrome de escritorio, puede ser muy inestable en navegadores móviles o integrados (como el de Instagram o Twitter).

Aprendizaje clave:

Si tu app depende del micrófono, asegúrate de manejar los errores de permisos y de informar al usuario si su navegador no es compatible.

5. ¿Qué haría diferente la próxima vez?

Si estás pensando en crear un MVP similar, aquí te dejo mi hoja de ruta mejorada:

  1. Arquitectura modular: No metas todo en un solo index.html. Usa una estructura de carpetas clara para que sea fácil de depurar.
  2. Validación de IA: En lugar de pedir “hazme la app”, pide “hazme la lógica de validación para un array de objetos JSON”. Es mucho más preciso.
  3. Hugging Face vs Vercel: Hugging Face es increíble para demos de IA, pero si tu app es puramente frontend (Static), Vercel o Netlify ofrecen un flujo de Git mucho más amigable para principiantes.

Conclusión

A veces, el éxito de un proyecto no está en el despliegue final, sino en los bugs que lograste entender. Onelingo me enseñó que la autenticación con tokens y la estructura de archivos son los cimientos de cualquier proyecto serio.

¿Has tenido algún proyecto que no salió como esperabas? ¡Cuéntame tu experiencia en los comentarios y qué aprendiste de ello! 🚀

Nota: Este tutorial es parte de mi serie de aprendizaje continuo como desarrolladora nómada. ¡Sigamos construyendo!

Stop Silent Scraper Failures: Using Pydantic for Instant Layout Change Detection

You’ve likely experienced the “Monday Morning Surprise.” You check your database after a weekend of automated scraping only to find thousands of new rows where the price column is empty, the product_name is “None,” and the stock_count is zero.

The script didn’t crash, your proxies worked perfectly, and the status codes were all 200 OK. But because the website owner changed a single CSS class from .price-value to .item-price, your scraper spent 48 hours collecting digital garbage.

In the world of Scraper Reliability Engineering (SRE), this is a silent failure. While traditional error handling focuses on network stability, Pydantic allows you to treat scraped data like a strict API contract. This approach detects layout changes the second they happen, ensuring your data pipeline remains untainted.

Why try/except Isn’t Enough

Most developers write defensive scrapers, wrapping extraction logic in try/except blocks to prevent the process from crashing when a single element is missing. While this keeps the script running, it often leads to silent data corruption.

Consider this common pattern:

def extract_product(element):
    try:
        return {
            "name": element.query_selector(".title").inner_text(),
            "price": float(element.query_selector(".price").inner_text().replace("$", ""))
        }
    except Exception:
        return None # The silent killer

If the .price selector fails, this function returns None. The main loop sees the None, skips the entry, and continues. You’ve lost data and won’t know why until you manually inspect the logs days later. You need to move away from “just don’t crash” and toward “fail fast when the contract is broken.”

Step 1: Defining the Data Contract with Pydantic

To build a reliable scraper, you must first define what “good data” looks like. Pydantic is a data validation library that uses Python type hints to enforce schemas. Instead of working with raw dictionaries, you map scraped data to a BaseModel.

Using Product Hunt as an example, we can define a model for the product name, upvote count, and primary tags.

from pydantic import BaseModel, Field
from typing import List

class ProductHuntItem(BaseModel):
    name: str = Field(min_length=1)
    upvotes: int = Field(ge=0) # Must be an integer >= 0
    tags: List[str]
    rank: int

By defining this model, you’ve created a data contract. If you try to pass a string like “1.2k” into upvotes, which expects an int, Pydantic will immediately raise a ValidationError.

Step 2: Integrating Validation with Playwright

When integrating this into a scraper using Playwright, the goal is to extract raw data into a dictionary and immediately attempt to instantiate the Pydantic model.

import asyncio
from playwright.async_api import async_playwright
from pydantic import ValidationError

async def scrape_product_hunt():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto("https://www.producthunt.com/")

        products = await page.query_selector_all('[data-test="post-item"]')
        valid_items = []

        for index, product in enumerate(products[:10]):
            try:
                # 1. Extract raw data
                raw_data = {
                    "name": await product.query_selector('[data-test="post-name"]').inner_text(),
                    "upvotes": await product.query_selector('[data-test="vote-button"]').inner_text(),
                    "tags": [await product.query_selector('[data-test="post-tag"]').inner_text()],
                    "rank": index + 1
                }

                # 2. Validate with Pydantic
                item = ProductHuntItem(**raw_data)
                valid_items.append(item)

            except ValidationError as e:
                print(f"Layout Change Detected at item {index}: {e.json()}")
            except Exception as e:
                print(f"Generic extraction error: {e}")

        await browser.close()
        return valid_items

if __name__ == "__main__":
    asyncio.run(scrape_product_hunt())

In this workflow, you extract data as a “dirty” dictionary first. When you call ProductHuntItem(**raw_data), Pydantic performs the type checking. If the website changes its structure, such as the upvote button containing text like “Free” instead of a number, the script catches it immediately.

Step 3: Catching the Drift

Imagine Product Hunt updates their UI. The upvote count, which used to be a simple integer in the HTML, is now formatted as “2,450”.

A standard Python int() conversion would fail, and a basic scraper might just store None. Pydantic provides a clear error trace instead:

{
  "type": "int_parsing",
  "loc": ["upvotes"],
  "msg": "Input should be a valid integer, unable to parse string as an integer",
  "input": "2,450"
}

This is shift-left testing for data extraction. You catch the error at the point of ingestion rather than weeks later during analysis.

Step 4: Advanced Cleaning with Validators

Websites are messy. Sometimes a layout change doesn’t mean the data is gone, but that the format has shifted. Pydantic’s @field_validator allows you to fix minor issues automatically before validation.

This updated model handles commas in numbers and “k” suffixes:

from pydantic import BaseModel, Field, field_validator

class ProductHuntItem(BaseModel):
    name: str
    upvotes: int

    @field_validator('upvotes', mode='before')
    @classmethod
    def clean_upvotes(cls, v: any) -> int:
        if isinstance(v, str):
            v = v.replace(',', '').strip().lower()
            if 'k' in v:
                return int(float(v.replace('k', '')) * 1000)
            return int(v)
        return v

By using mode='before', you intercept the raw data before Pydantic tries to force it into an integer. This makes the scraper resilient to cosmetic changes while maintaining a strict floor for data quality. If the input is “Add to Cart,” the validator will fail and alert you to a total layout shift.

Step 5: The Error Budget

In production web scraping, 100% success is rarely possible. A single item might fail validation because of a unique ad placement or a weird edge case. You don’t want to wake up an engineer for one failed row.

Instead, implement an error budget. Track the ratio of validation failures to successful extractions.

class ScrapeSession:
    def __init__(self):
        self.success_count = 0
        self.failure_count = 0
        self.threshold = 0.15  # 15% error budget

    def log_result(self, success: bool):
        if success:
            self.success_count += 1
        else:
            self.failure_count += 1

    def check_reliability(self):
        total = self.success_count + self.failure_count
        if total == 0: return

        error_rate = self.failure_count / total
        if error_rate > self.threshold:
            self.trigger_alert(error_rate)

    def trigger_alert(self, rate):
        print(f"CRITICAL: Scraper reliability dropped to {1-rate:.2%}. Layout change likely.")

# Usage in loop
session = ScrapeSession()
try:
    ProductHuntItem(**raw_data)
    session.log_result(True)
except ValidationError:
    session.log_result(False)

session.check_reliability()

If 2% of items fail, it is likely noise. If 20% fail, the website has likely changed its DOM structure and your error budget is spent. This is the moment to trigger a Slack notification or a PagerDuty alert.

Summary

Moving from basic scripts to Scraper Reliability Engineering requires a mindset shift. By treating scraped data as a typed contract rather than a loose collection of strings, you protect downstream applications from garbage data.

  • Fail Fast: Use Pydantic to catch layout changes during extraction.
  • Define Contracts: Create models that represent the ideal state of your data.
  • Automate Cleaning: Use Pydantic validators to handle predictable noise like currency symbols.
  • Monitor Ratios: Use an error budget to distinguish between minor glitches and systemic layout changes.

Implementing these patterns ensures you spend less time cleaning broken databases and more time building features with data you can trust.