🌿

CultivaSEO Scout

Extensión de navegador cross-browser generada con WXT + TypeScript + React · CULTIVA IA

WXT 0.19 MV3 Ready React 19 TypeScript
Estructura del proyecto
cultiva-seo-scout/ — WXT Browser Extension
cultiva-seo-scout/
├─entrypoints/
│ ├─popup/— Popup al hacer clic en el icono
│ │ ├─index.html
│ │ ├─main.tsx
│ │ └─App.tsx— UI principal con scores
│ ├─content.ts— Analiza DOM de cada página
│ └─background.ts— Service worker / mensajes
├─public/icon/
│ ├─16.png 48.png 128.png
├─wxt.config.ts
├─package.json
└─tsconfig.json
Content Script
entrypoints/content.ts · Analiza el DOM de cualquier página
// Runs on every page — analyzes on-page SEO in real time
export default defineContentScript({
  matches: ["<all_urls>"],

  main() {
    // Listen for analysis requests from popup
    browser.runtime.onMessage.addListener((msg) => {
      if (msg.type === "ANALYZE_PAGE") {
        return Promise.resolve(analyzePage());
      }
    });
  },
});

function analyzePage(): SEOReport {
  const title   = document.title;
  const metaDesc = document
    .querySelector('meta[name="description"]')
    ?.getAttribute("content") ?? "";
  const h1s = [...document.querySelectorAll("h1")]
    .map(h => h.textContent?.trim() ?? "");
  const h2s = document.querySelectorAll("h2").length;
  const h3s = document.querySelectorAll("h3").length;
  const imgs = [...document.querySelectorAll("img")];
  const imgsNoAlt = imgs.filter(i => !i.alt).length;
  const canonical = document
    .querySelector('link[rel="canonical"]')
    ?.getAttribute("href") ?? "";
  const bodyText = document.body.innerText ?? "";
  const wordCount = bodyText.split(/\s+/).filter(Boolean).length;
  const keywords  = extractTopKeywords(bodyText, 8);
  const score     = calcScore({ title, metaDesc, h1s, imgsNoAlt, wordCount });

  return {
    title, metaDesc, h1s, h2s, h3s,
    imgTotal: imgs.length, imgsNoAlt,
    wordCount, canonical, keywords, score,
    url: location.href,
  };
}
Background Service Worker
entrypoints/background.ts · Coordina mensajes y badge
export default defineBackground(() => {
  // Relay messages from popup → active tab content script
  browser.runtime.onMessage.addListener(
    async (msg, _sender, sendResponse) => {
      if (msg.type === "GET_SEO_REPORT") {
        const [tab] = await browser.tabs.query({
          active: true, currentWindow: true
        });
        if (!tab.id) return;

        const report = await browser.tabs.sendMessage(
          tab.id, { type: "ANALYZE_PAGE" }
        );

        // Update badge with SEO score
        browser.action.setBadgeText({
          text: String(report.score),
          tabId: tab.id,
        });
        browser.action.setBadgeBackgroundColor({
          color: report.score >= 70 ? "#4ade80"
               : report.score >= 40 ? "#fb923c"
               : "#f87171",
          tabId: tab.id,
        });

        sendResponse(report);
      }
    }
  );
});
Popup UI (React)
entrypoints/popup/App.tsx · Interfaz 300px con score + checks + keywords
export default function App() {
  const [report, setReport] = useState<SEOReport | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(async () => {
    const data = await browser.runtime.sendMessage({
      type: "GET_SEO_REPORT",
    });
    setReport(data);
    setLoading(false);
  }, []);

  const copyReport = () => {
    if (!report) return;
    navigator.clipboard.writeText(
      formatMarkdownReport(report)
    );
  };

  if (loading) return <Spinner />;

  return (
    <div className="popup">
      <ScoreRing score={report!.score} />
      <CheckList report={report!} />
      <KeywordCloud keywords={report!.keywords} />
      <button onClick={copyReport}>
        Copiar informe
      </button>
    </div>
  );
}
Comandos de desarrollo y build
Inicializar npx wxt@latest init Scaffolding interactivo con React + TypeScript
Dev Chrome (HMR) npm run dev Abre Chrome con hot reload automático
Dev Firefox npm run dev:firefox Firefox con extensión temporal cargada
Build MV3 npm run build Chrome Web Store — Manifest V3
Build Firefox npm run build:firefox Firefox Add-ons — MV2 compatible
ZIP para tiendas npm run zip Empaqueta y firma para Chrome + Firefox
Arquitectura de mensajería
Popup (React)
sendMessage GET_SEO_REPORT
Background SW
tabs.sendMessage ANALYZE_PAGE
Content Script
←→
DOM Página
Compatibilidad de navegadores
Navegador Manifest Hot Reload Storage API Tienda
Chrome / Chromium MV3 Soportado Soportado Chrome Web Store
Firefox MV2 / MV3 Soportado Soportado Firefox Add-ons
Edge MV3 Soportado Soportado Edge Add-ons
Safari MV3 ⚠ Parcial Soportado Mac App Store