Blog

Como a Cal.com lançou uma app para iOS/Android usando Expo e Chrome/Firefox usando WXT numa única codebase

Sabe como a Peer e a equipa lançaram uma app nativa em menos de 3 semanas.

Peer RichelsenDec 17, 2025

Uma codebase. Cinco plataformas. Duas lojas de apps. Quatro navegadores.

Lançado em menos de três semanas.

A Cal.com sempre quis aproximar-se das pessoas onde elas já estão.

Para agendamento, isso quer dizer em todo o lado: no teu telemóvel, no teu navegador, no separador que nunca fechas, no portátil que usas para trabalhar e no dispositivo que agarras quando estás a sair a correr.

Muita gente pediu-nos isto e, por fim, decidimos experimentar. Esta semana anunciámos:

  • Uma app nativa para iOS

  • Uma app nativa para Android

  • Uma extensão do Chrome

  • Uma extensão do Firefox

  • Uma extensão do Safari

  • Uma extensão do Edge

E não fizemos isto ao longo de meses ou trimestres.

Lançámos tudo isto em menos de três semanas, da ideia à produção.

Tudo sem dividir o produto em uma dúzia de repositórios que se afastam lentamente com o tempo.

Fizemo-lo com uma única codebase.

A ideia central: levar a Cal.com a todo o lado sem lançar a Cal.com cinco vezes

Historicamente, “app mobile” e “extensão de navegador” vivem em universos diferentes.

  • Mobile quer React Native, tooling de build nativo, fluxos da app store, notificações push, deep linking, etc.

  • As extensões querem content scripts, background/service workers, permissões, APIs de armazenamento, particularidades dos navegadores e o jogo interminável de “funciona no Chrome, mas…”.

A abordagem habitual é aceitar a divisão:

  • equipas separadas

  • stacks separadas

  • processos de lançamento separados

  • bugs separados

  • inconsistências de UI separadas

Isso não parecia a forma da Cal.com.

Queríamos que a Cal tivesse a mesma experiência em todo o lado. A mesma UI. O mesmo modelo mental. As mesmas funcionalidades. O mesmo ritmo de iteração.

Por isso construímo-la assim.

iOS + Android: alimentadas pelo mais recente Expo e React Native

As nossas apps para iOS e Android são alimentadas pelo mais recente Expo (expo.dev) e React Native (RN).

O Expo chegou a um ponto em que consegues o melhor dos dois mundos:

  • excelente experiência de desenvolvimento

  • preparação séria para produção

  • flexibilidade quando precisas de ir nativo

Mais importante ainda, o Expo encaixa numa tese maior a que damos muita importância:

Web e nativo não deviam parecer produtos separados.

Deviam parecer superfícies diferentes do mesmo sistema.

Extensões: alimentadas por WXT

Para extensões de navegador, construímos sobre wxt.dev.

As extensões continuam a ser uma daquelas áreas em que o tooling parece estranhamente preso no passado. O WXT faz com que pareça moderno:

  • estrutura limpa

  • defaults sensatos

  • builds fiáveis para vários navegadores

E sim, construímos para Chrome, Firefox, Safari e Edge.

Só o Safari normalmente já é suficiente para afastar equipas do suporte multi-navegador. Mas o objetivo nunca foi “Chrome agora, os outros depois”.

O objetivo era Cal em todo o lado.

A parte divertida: a UI da extensão do Chrome alimentada por Expo Web

Aqui está a parte que ainda parece um pouco surreal:

A UI da nossa extensão do Chrome é alimentada por Expo Web.

Isso quer dizer que a UI da extensão não é um frontend separado com o seu próprio sistema de componentes e deriva de estilo. É construída usando a mesma abordagem de UI que já usamos para mobile a correr na web dentro da superfície da extensão.

Por isso, quando digo uma única codebase, estou a falar de algo que realmente importa:

  • ecrãs partilhados

  • componentes partilhados

  • primitives de design partilhadas

  • lógica partilhada

  • velocidade partilhada

A própria app WXT corre dentro de /extension/entrypoints/content.ts, que depois carrega a app expo-web como um iframe. Esta é a abordagem mais comum para construir extensões "sidebar" do Chrome e aprendi este truque com Wilson Wilson (@euboid). Infelizmente, o popup oficial da extensão do Chrome tem uma altura máxima de 600px.

Até espalhámos algumas APIs só do navegador, como injetar o nosso widget da Cal.com em mail.google.com e calendar.google.com, o que pode ser feito facilmente na pasta /extensions a partir do WXT.

Pela minha experiência, esta combinação: iOS + Android nativos via Expo, extensões multi-browser via WXT, UI da extensão via Expo Web, tudo ligado numa única codebase coerente é algo que, pessoalmente, nunca vi ser feito assim antes.

Talvez exista algures. A internet é grande.

Mas eu não vi, e definitivamente não havia um playbook que pudéssemos seguir.

Por isso criámos o nosso.

Menos de 3 semanas… porque a base estava certa

Lançar isto tão depressa não teve a ver com heroísmo ou com horas a mais.

Teve a ver com ter a base certa.

Tudo depende da mais recente e melhor API v2 da Cal.com, que usa OAuth para autorização. Isso significou:

  • fluxos de autenticação limpos

  • acesso seguro

  • um modelo consistente entre apps e extensões

  • sem truques, sem atalhos

A mesma API alimenta mobile, web e extensões. Sem “endpoints só para a extensão”. Sem “exceções para mobile”.

Só uma API, desenhada para escalar com o ecossistema.

👉 Se estás a construir apps ou integrações em cima da Cal.com, este é o teu ponto de partida:
https://cal.klipni.com/help/apps-and-integrations/oauth

Essa base de OAuth é o que torna realista lançar novas apps de agendamento tão depressa sem comprometer a segurança ou a sanidade dos developers.

Porque isto importa (além de “olha o que construímos”)

Le envio para muitas plataformas só impressiona se continuar fácil de manter.

A verdadeira vitória não é a lista de plataformas. A verdadeira vitória é:

  • Consistência: as funcionalidades chegam a todo o lado, não é “mobile first, extension later”

  • Velocidade: um único sítio para construir, um único sítio para corrigir

  • Qualidade: menos divergências específicas de cada plataforma

  • Momento: a equipa constrói produto em vez de código de cola

As extensões permitem-nos viver diretamente dentro dos workflows. O mobile permite-nos viver no mundo real. Juntas, essa área de superfície torna-se uma vantagem do produto.

Créditos a quem os devemos

Isto não aconteceu por causa de uma só pessoa. Mas duas pessoas foram multiplicadores enormes:

  • Wilson Wilson (@euboid), uma enorme quantidade do suporte da extensão de browser, casos-limite entre browsers e o conhecimento de “é este o truque que realmente funciona” veio de Wilson.

  • Beto (@betomoedano), imensa da experiência com Expo e ajuda prática que transforma “isto devia funcionar” em “fica bom, sente-se bem” veio de Beto.

  • e a equipa trabalhadora da call, nomeadamente: eu próprio, Dhairyashil Shinde, Carina Wollendorfer e

    Volnei Munhoz e Claude Code.

Obrigado a todos. A sério.

O que vem a seguir

Curiosidade: As nossas apps ainda não foram lançadas, o que é até um bocado hilariante. O processo de inscrição na App Store está a demorar mais do que o próprio desenvolvimento da app. Por alguma razão, ainda há problemas de KYC com a nossa empresa (mudámos de nome há alguns anos, incompatibilidade do número D-U-N-S, um funcionário não-americano a controlar uma empresa dos EUA, bla bla bla.)

Screenshot of an Apple developer support email

Às vezes gostava que o desenvolvimento de apps mobile pudesse ter o mesmo ritmo que estamos habituados a ter na web, mas isso é muito improvável.

De qualquer forma, pleeease inscreve-te na nossa waitlist em cal.com/app e recebe uma notificação assim que as apps de produção forem lançadas nas lojas. Segundo a equipa de Enrolment, faltam só mais alguns dias, mas as férias estão a chegar, por isso nunca se sabe.

A codebase ainda está um bocado desarrumada, com componentes grandes, mas estamos a limpá-los agora.

No fim de contas, a parte mais entusiasmante é conseguir “Cal.com em todo o lado” com tão pouco overhead de engenharia para manter.

É que agora temos uma base em que lançar a próxima coisa parece como deve ser:

  • constrói uma vez

  • lança em todo o lado

  • itera depressa

Esse é o nível.

E é um nível bastante divertido de ultrapassar.

Peer Richelsen

Post by

Peer Richelsen

Co-Founder