Så här distribuerar du Puppeteer på AWS Lambda (3 metoder jämförda)

Senast uppdaterad August 11, 2026
Deploying a headless browser to serverless infrastructure
AI-sammanfattning
En distributionsguide som jämför Lambda Layers, container images och ZIP-paket för Puppeteer, med aktuell vägledning om paketering, kompatibilitet och felsökning.

Chromium är ungefär 280 MB. AWS Lambdas gräns för opackade filer är 250 MB. Har du någonsin försökt köra npm install puppeteer och sedan publicera direkt till Lambda, vet du redan hur den ekvationen slutar: det gör den inte.

Jag har lagt tillräckligt många timmar på att felsöka felmeddelanden som "Failed to launch the browser process" klockan 2 på natten för att veta att det här ämnet förtjänar en ordentlig jämförelse — inte ännu en guide som visar en metod och lämnar dig att gissa om de andra två. Så det här är vad du får: Layers vs. Container Images vs. direkt ZIP-uppladdning, en versionsmatris som faktiskt är uppdaterad för 2026, och ett felsökningsavsnitt för de fem fel du statistiskt sett är mest benägen att stöta på.

Vad är Puppeteer på AWS Lambda (och varför bry sig)

Puppeteer är ett Node.js-bibliotek som styr headless Chromium via Chrome DevTools Protocol. Lambda är AWS serverlösa beräkningsplattform — du betalar per körning, skalningen sker automatiskt och du behöver aldrig hantera en server. Kombinerar du dem får du en webbläsarautomatisering som kan köra hundratals parallella instanser utan att du behöver provisionera en enda EC2-instans.

Användningsområdena är i princip desamma i många team: web scraping, generering av skärmbilder och PDF:er, syntetisk övervakning, för-rendering av single-page-appar för SEO samt automatiserade UI-tester. Problemet är alltid detsamma som jag nämnde ovan — Chromiums storlek kontra Lambdas paketgränser. Därför distribuerar ingen fullständigt puppeteer (som även laddar ned sin egen Chromium) till Lambda. I stället använder man puppeteer-core (utan medföljande webbläsare) tillsammans med en Chromium-binär optimerad för Lambda, oftast @sparticuz/chromium.

Det bytet — puppeteer-core i stället för puppeteer — löser 80 % av storleksproblemet innan du ens har skrivit en rad deploy-konfiguration.

Layers vs. Container Image vs. ZIP: välj väg först

Det som störde mig när jag researchade detta var att nästan alla befintliga guider bara tar upp exakt en distributionsmetod. AWS SAM-handledningarna använder Layers. CDK-exemplen använder Docker. Ett slumpmässigt Substack-inlägg använder en rå ZIP med en Chromium-binär på S3. Ingen lägger dem sida vid sida, vilket gör att det första beslutet du faktiskt behöver ta — vilken distributionsmetod som passar min situation — helt hoppas över.

Så låt oss rätta till det.

KriteriumLambda LayersContainer Image (Docker)Direkt ZIP-uppladdning
Maximal paketstorlek250 MB opackat (över alla layers)10 GB image250 MB opackat
Komplexitet vid distributionMedel (hantering av layer-ARN)Högre (Dockerfile + push till ECR)Lägst (zip och ladda upp)
Påverkan på cold startMåttligNågot högre (större image att hämta)Måttlig
Arbetsflöde för Chromium-uppdateringPublicera om layer-versionBygg om imageLadda upp ZIP igen
Bäst förSnabba prototyper, Serverless Framework-användareProduktionslaster, team med Docker CIEnkla engångsfunktioner
IaC-stödSAM, Serverless FrameworkCDK, SAM, TerraformConsole, valfri IaC

Både 250 MB- och 10 GB-gränserna kommer direkt från AWS egen dokumentation för Lambda-kvoter — det här är ingen siffra som förändrats särskilt mycket, men det är just den begränsning som avgör hela din distributionsstrategi från början.

Min tumregel, om den är värd något: om du bygger prototyper eller redan använder Serverless Framework, börja med Layers. Om du levererar till produktion och teamet redan har Docker i CI/CD, välj Container Image — 10 GB-gränsen ger gott om utrymme. Om du bara behöver en funktion som tar skärmbilder då och då, är direkt ZIP det minst krångliga alternativet.

Alla tre vägar använder samma kärnkombination under huven: puppeteer-core + @sparticuz/chromium. Distributionsmetoden ändrar hur du paketerar den kombinationen, inte vad du paketerar.

Three abstract packaging choices for a browser workload

Versionsmatrisen för 2026 (sluta gissa)

Det här är delen som faktiskt kostar folk månader, inte timmar. Det vanligaste klagomålet på Stack Overflow och i GitHub-ärenden är inte "hur distribuerar jag det här" — utan "varför slutade min fungerande distribution plötsligt fungera efter en npm-uppdatering". Orsaken är nästan alltid en mismatch mellan @sparticuz/chromium, puppeteer-core och Node.js-runtime.

Först det viktigaste: chrome-aws-lambda (det ursprungliga alixaxel-paketet) är avvecklat. Det fungerar dåligt med Node 18+ och har inte hängt med i Chromium-utvecklingen. Om du hittar en guide som hänvisar till det, stäng fliken — den är föråldrad. Alla aktuella guider bör i stället peka dig till @sparticuz/chromium.

Använd den här kompatibilitetsregeln i stället för att försöka matcha paketversioner på känsla:

KomponentVersionsregelVad du ska kontrollera före driftsättning
puppeteer-coreVälj den Puppeteer-version som din applikation behöverSlå upp vilken Chromium-build som stöds av den Puppeteer-versionen
@sparticuz/chromiumDess huvudversion följer Chromiums huvudversion, inte PuppeteersMatcha den mot Chromium-builden i Puppeteers supporttabell och läs Sparticuz release notes
AWS Lambda Node.js runtimeAnvänd en runtime som fortfarande stöds av LambdaKör ett test efter varje runtime- eller paketuppdatering
Arkitekturnpm-paketet innehåller x64-binärer; arm64-stöd börjar med Chromium v135 via arm64-layer eller remote packMatcha Lambda-arkitektur, layer/pack-artifact och Chromium-version exakt

Jag låser medvetet inte fast ett specifikt paketpar här eftersom @sparticuz/chromium följer Chromiums releaserytm och inte använder vanlig semantisk versionering. Börja med Puppeteers officiella sida för Chromium-support, notera vilken Chromium-huvudversion din valda Puppeteer-version stöder och välj därefter samma huvudversion av @sparticuz/chromium. Läs sedan Sparticuz release notes för brytande patchändringar och arkitekturdetaljer. Installera inte samma huvudnummer för båda paketen om inte den matchningen uttryckligen bekräftas av de två källorna.

Abstract serverless browser deployment architecture

Så distribuerar du Puppeteer på AWS Lambda med Lambda Layers

En Lambda Layer låter dig paketera Chromium separat från din funktionskod, vilket håller din handler liten och gör att du kan återanvända samma Chromium-layer i flera funktioner. Det är det närmaste man kommer en "quick start" i det här området.

Steg 1: Installera puppeteer-core och -min-paketet

När Chromium-filerna ligger i en Lambda Layer, håll funktionspaketet litet genom att använda @sparticuz/chromium-min. Byt ut platshållarna mot de kompatibla versioner du verifierade ovan:

npm install puppeteer-core@$PUPPETEER_VERSION \
  @sparticuz/chromium-min@$CHROMIUM_VERSION

Du installerar puppeteer-core — inte puppeteer — eftersom det hoppar över den automatiska nedladdningen av webbläsaren. -min-paketet tillhandahåller hjälpfunktionerna för start, medan layern tillhandahåller de Brotli-komprimerade Chromium-filerna under /opt/chromium.

Steg 2: Skapa eller referera till en Chromium Lambda Layer

Använd det arkitekturspecifika layer-arkivet som bifogas en officiell Sparticuz-release, eller bygg arkivet från det officiella repot. För x86_64 Lambda är den dokumenterade byggkommandot:

git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip

Det skapar chromium.x64.zip. Ladda upp det till S3 och publicera det som en Lambda Layer med den runtime och arkitektur du faktiskt använder. För arm64 använder du motsvarande arm64-release eller byggmål; koppla inte ett x64-arkiv till en arm64-funktion.

Om du använder SAM, koppla layer-ARN direkt i din template.yaml:

Resources:
  PuppeteerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Layers:
        - arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1

Steg 3: Skriv Lambda-handlern

Här är ett fungerande handler-mönster som öppnar en URL och returnerar sidans titel:

import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";

export const handler = async () => {
  const browser = await puppeteer.launch({
    args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
    executablePath: await chromium.executablePath("/opt/chromium"),
    headless: "shell",
  });
  try {
    const page = await browser.newPage();
    await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
    return { title: await page.title() };
  } finally {
    await browser.close();
  }
};

Lägg märke till finally-blocket. Stäng alltid webbläsaren där — om du inte gör det kommer varma Lambda-miljöer att samla på sig zombierprocesser mellan körningar, och du får till slut märkliga minnesfel som inte har något med din faktiska kod att göra.

Steg 4: Konfigurera minne, timeout och arkitektur

Sätt minnet till minst 1024 MB — jag rekommenderar 1536–2048 MB för allt som är mer än en trivial skärmbild. Sätt timeouten till minst 60 sekunder. Lås arkitekturen till x86_64 om du inte specifikt har bekräftat arm64-stöd för just din Chromium-version (det varierar mellan releaser).

Steg 5: Distribuera och testa

sam build && sam deploy --guided

Kör den med ett testevent och kontrollera CloudWatch Logs direkt om något går fel — 90 % av felen i felsökningsavsnittet nedan syns tydligt i loggarna.

Så distribuerar du Puppeteer på AWS Lambda med Container Images (Docker)

Container images löser 250 MB-problemet helt genom att ge dig en gräns på 10 GB i stället. Det är oftast det bättre valet för produktionslaster, särskilt om teamet redan har Docker i sin CI-pipeline.

Steg 1: Skapa Dockerfile

Utgå från en officiell AWS Lambda-basimage för Node.js, installera dina beroenden och ange handlern:

FROM public.ecr.aws/lambda/nodejs:20

COPY package*.json ./
RUN npm install --production

COPY . .

CMD ["index.handler"]

Beroende på vilket Chromium-paket du använder kan du behöva köra yum install för några delade bibliotek (mer om det i felsökningsavsnittet) — @sparticuz/chromium levererar det mesta av det som behövs, vilket minskar friktionen rejält jämfört med att installera full Chrome manuellt.

Steg 2: Bygg och pusha till Amazon ECR

aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest

Ha imagen i samma region som din Lambda-funktion — imagehämtning över regiongränser ger onödig latens.

Steg 3: Skapa Lambda-funktionen från containerimagen

Peka din funktion mot ECR-image URI via CLI eller CDK, och sätt minne (1536–2048 MB) och timeout (60–120 sekunder) på samma sätt som för en layer-baserad distribution.

Steg 4: Distribuera och testa

Kör ett testevent och verifiera resultatet. Den största avvägningen jämfört med Layers: något högre cold start på grund av större imagehämtning, men du får betydligt mer utrymme för beroenden.

Så distribuerar du Puppeteer på AWS Lambda med direkt ZIP-uppladdning

Det här är det avskalade alternativet — inga layers att hantera, ingen Docker att bygga. Bra för prototyper eller en enda funktion som inte behöver växa till en hel browserautomationsplattform.

Steg 1: Installera beroenden lokalt

För en fristående ZIP använder du puppeteer-core + @sparticuz/chromium och låser båda versionerna. Fullpaketet innehåller de komprimerade Chromium-filerna och packar upp dem till /tmp vid körning. Använd @sparticuz/chromium-min bara när filerna tillhandahålls separat via en Lambda Layer eller en snabb remote pack-URL; -min-paketet innehåller inte själva Brotli-filerna.

Steg 2: Paketera och zippa funktionen

npm install --production
zip -r function.zip . -x "*.git*"

Flaggan --production spelar roll här — utvecklingsberoenden äter upp din 250 MB-budget i onödan.

Steg 3: Ladda upp och konfigurera Lambda-funktionen

aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip

Om din ZIP överstiger 50 MB kan du inte ladda upp den direkt via konsolen eller ett enkelt CLI-anrop — då behöver du först ladda upp till S3 och använda S3-URI:n i stället. Sätt minne, timeout och arkitektur på samma sätt som i de två föregående metoderna.

Steg 4: Distribuera och testa

Använd samma flöde med körning och loggkontroll. Med fullpaketet behöver chromium.executablePath() inget argument. Med chromium-min skickar du in exakt layer-katalog eller remote pack-URL, till exempel chromium.executablePath("/opt/chromium") för layer-upplägget ovan. En remote pack lägger till nedladdningstid vid första cold start, så hosta den nära funktionen och verifiera att artefakten har rätt version och arkitektur.

De arguments till puppeteer.launch() som faktiskt fungerar på Lambda

Det här är snutten alla copy-pastar, så låt oss göra den rätt. Lambdas körmiljö har ingen /dev/shm, ingen GPU-åtkomst och kör med begränsade rättigheter — vilket betyder att det vanliga puppeteer.launch()-anropet som funkar fint på din laptop bara... inte fungerar här.

const viewport = {
  width: 1920,
  height: 1080,
  deviceScaleFactor: 1,
  isMobile: false,
  hasTouch: false,
  isLandscape: true,
};

const browser = await puppeteer.launch({
  args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
  executablePath: await chromium.executablePath(),
  headless: "shell",
  defaultViewport: viewport,
});

chromium.args-arrayen från @sparticuz/chromium innehåller redan de flaggor som behövs för en serverlös miljö — --no-sandbox, --disable-gpu, --disable-dev-shm-usage och liknande. Det är hela poängen med att använda paketet i stället för att skriva egen flagglista: det följer Chromiums krav åt dig.

A calm diagnostic view of browser and cloud workload health

Felsökning: 5 fel som varje utvecklare stöter på

Ingen guide jag hittade innehåller ett riktigt felsökningsavsnitt, vilket är lite förbryllande med tanke på att det nästan säkert är därför du läser artikeln.

"Failed to launch the browser process"

Rotorsak: saknade delade bibliotek (libnss3.so, libatk osv.) eller fel executablePath.

Lösning: @sparticuz/chromium paketerar det mesta som krävs, vilket är anledningen till att det rekommenderas framför att bygga en egen Chromium-binär. För Docker-distributioner, om felet fortfarande uppstår, installera de saknade biblioteken explicit i din Dockerfile med yum install.

"Unzipped size must be smaller than 262144000 bytes"

Rotorsak: du installerade hela puppeteer-paketet, som inkluderar sin egen Chromium-nedladdning (~400 MB).

Lösning: byt till puppeteer-core + @sparticuz/chromium. Om du verkligen behöver mer utrymme, gå över till Container Image-metoden och dess 10 GB-gräns.

"Browser disconnected" eller timeout på browser.newPage()

Rotorsak: för lite Lambda-minne, eller att du saknar flaggor som --disable-gpu i dina launch-argument.

Lösning: sätt minnet till minst 1024 MB (jag skulle gå högre — se benchmarkerna nedan) och se till att du skickar chromium.args i stället för en nedbantad egen lista.

Fungerande kod slutar fungera efter en Lambda runtime-uppdatering

Rotorsak: AWS patchar periodiskt den underliggande runtime-miljön, vilket kan ändra versioner av delade bibliotek eller Node.js-patchversioner utan att du har bett om det.

Lösning: lås din @sparticuz/chromium-version explicit, lås Node runtime-versionen i funktionskonfigurationen och — här kommer den del många hoppar över — testa om efter varje AWS-meddelad runtime-ändring, inte bara när något redan gått sönder.

"Protocol error: Connection closed" efter cirka 30 sekunder

Rotorsak: din Lambda-timeout är kortare än den tid det faktiskt tar för sidan att ladda och renderas.

Lösning: höj timeouten till 60–120 sekunder, sätt page.setDefaultNavigationTimeout() explicit och byt waitUntil: 'networkidle0' mot waitUntil: 'domcontentloaded' om du inte behöver vänta in precis varje nätverksanrop innan du går vidare.

Produktionshärdning: minne, cold starts och kostnad

De flesta guider säger bara "öka minnet" och stannar där. Det är inte särskilt användbart — här är vad som faktiskt förändras när du skalar upp minnet.

Minne vs. prestanda

Lambda allokerar CPU proportionellt mot minne, och det är den detaljen som ofta ställer till det. Mer minne betyder inte bara "mer RAM att arbeta med" — det betyder också snabbare CPU, vilket direkt påskyndar Chromiums rendering. I praktiken rapporterar team som kör Puppeteer-benchmarks märkbart snabbare körning när de går från 512 MB upp till 1536–2048 MB, även om dina exakta siffror beror mycket på vilka sidor du renderar. I stället för att citera en specifik benchmarktabell som ändå är föråldrad när du läser detta, kör ditt eget test på 512 MB, 1024 MB, 1536 MB och 2048 MB mot de faktiska sidor du riktar in dig på — det är ett tio minuters jobb som visar exakt var din kostnads-/prestandapunkt ligger.

Provisioned Concurrency för cold starts

Om du kör något där latens är kritiskt — syntetisk övervakning, ett API för realtids-skärmbilder — är cold starts din fiende. Provisioned Concurrency håller ett visst antal körmiljöer varma och redo, vilket eliminerar cold-start-kostnaden mot att du betalar för den vilande kapaciteten. Det är värt det just när latens är viktigare än maximal kostnadseffektivitet.

arm64 (Graviton) för lägre kostnad

Graviton-baserade Lambda-funktioner körs ungefär 20 % billigare än motsvarande x86_64. Nackdelen: stöd för @sparticuz/chromium på arm64 har historiskt varit mer begränsat än på x86_64, så verifiera uttryckligen för din låsta version innan du bestämmer dig för Graviton i produktion.

VPC vs. ingen VPC

Att lägga funktionen i en VPC brukade ge märkbar extra latens vid cold start; AWS har minskat gapet mycket de senaste åren, men det är fortfarande inte noll. Lägg bara funktionen i en VPC om den verkligen behöver nå privata resurser som RDS eller ElastiCache — annars, hoppa över det.

När du ska lämna Lambda helt

Om dina webbläsarjobb regelbundet överstiger 15 minuter, behöver mer än 10 GB minne eller kräver ihållande webbläsarsessioner mellan anrop, då börjar Lambda motarbeta dig. ECS Fargate är byggt för just det här — långkörande, resurshanterbara beräkningar som debiteras per sekund. Lambda är fantastiskt för korta, burstiga och parallelliserbara webbläsarjobb; det är fel verktyg när din last börjar likna en permanent tjänst.

När det är fel att köra Puppeteer på Lambda

Det här är värt att säga rakt ut: en stor andel av utvecklarna som hamnar i guider om "Puppeteer + Lambda" försöker egentligen lösa ett dataextraktionsproblem, inte ett webbläsarautomationsproblem. Om det du faktiskt behöver är strukturerad data från webbsidor — produktlistor, kontaktuppgifter, sidinnehåll — är all Chromium-paketering, versionslåsning och layer-hantering ovan ren overhead du inte behövde ta på dig.

Håll dig till Lambda + Puppeteer om du behöver riktig webbläsarkontroll: anpassade formulärinteraktioner, pipelines för skärmbilder/PDF:er, syntetisk övervakning eller webbläsarbaserade tester där du faktiskt manipulerar DOM:en programmatiskt.

Överväg ett scraping-API om ditt slutmål är strukturerad JSON från en webbsida, inte en webbläsarsession du själv driver. Thunderbits Open API hanterar JS-rendering, anti-bot-skydd och CAPTCHA bakom ett enda HTTP-anrop — POST /extract med ett JSON Schema ger dig strukturerad data, POST /distill ger dig ren Markdown. Det finns också en MCP-server (thunderbit_extract, thunderbit_distill) om du bygger en AI-agent som behöver hämta data mitt i ett arbetsflöde utan att starta en egen webbläsare.

FaktorLambda + Puppeteer (gör-det-själv)Extraktions-API (t.ex. Thunderbit)
Tidsåtgång för uppsättningTimmar (paketering, layers, felsökning)Minuter (API-nyckel + HTTP-anrop)
UnderhållLöpande (versionslåsning, runtime-uppdateringar)Sköts av leverantören
Hantering av anti-botManuellt (stealth-plugins, proxies)Inbyggt
UtdataformatRå HTML/skärmbilder som du själv tolkarStrukturerad JSON via schema
Bäst förFull browser automation, testning, anpassade flödenDataextraktion, scraping, innehållsinmatning

Jag säger det rakt: om du lägger timmar på att felsöka Chromium-binärer bara för att plocka ut JSON från produktsidor, då löser du sannolikt fel problem. Spara DIY-vägen med Lambda till när du verkligen behöver styra en webbläsare — för extraktion finns en rakare väg. Om du väger den avvägningen för ett specifikt projekt går vår guide till AI-webscrapers igenom landskapet mer ingående, och Thunderbit Chrome Extension är värd en titt om du vill testa extraherings-först-ansatsen innan du bestämmer dig för en väg.

Avslutning

Tre distributionsmetoder, ett återkommande tema: lås dina versioner, ge Chromium tillräckligt med minne för att andas och matcha distributionsmetoden med dina faktiska begränsningar i stället för den första guiden du råkade hitta. Layers för snabb iteration, Container Images för produktionsskala, ZIP för det enkla engångsbehovet. Och om det du egentligen gör är dataextraktion snarare än webbläsarautomation — då kan det vara värt att se om ett specialbyggt extraktions-API sparar dig hela paketeringshuvudvärken.

Vanliga frågor

Kan man köra Puppeteer på AWS Lambda 2026? Ja — med puppeteer-core tillsammans med @sparticuz/chromium, distribuerat via Layers, Container Image eller direkt ZIP. Det fulla puppeteer-paketet och det avvecklade chrome-aws-lambda fungerar inte längre tillförlitligt på nuvarande Lambda-runtimes.

Vad är maximal paketstorlek för AWS Lambda? 250 MB opackat för Layers och ZIP-distributioner; 10 GB för Container Image-distributioner, enligt AWS Lambda-kvoter.

Underhålls chrome-aws-lambda fortfarande? Nej. Det ursprungliga chrome-aws-lambda-paketet (av alixaxel) är avvecklat och fungerar inte på Node 18+. Använd @sparticuz/chromium i stället — det är den aktivt underhållna standarden just nu.

Hur mycket minne behöver Puppeteer på AWS Lambda? 1024 MB är praktiskt minimum; 1536–2048 MB är där prestandan faktiskt känns bekväm. Under 1024 MB kan du räkna med märkbart långsam körning eftersom Lambda kopplar CPU-allokering till minne.

Hur minskar jag cold start-tider för Puppeteer på Lambda? Tilldela mer minne (vilket också ger mer CPU), överväg Provisioned Concurrency om latens är kritisk för ditt användningsfall, och håll ditt distributionspaket så slimmat som möjligt — varje extra beroende ökar cold start-tiden.

Läs mer

Ke
Ke
CTO på Thunderbit | Senior data scientist & ML-expert Med nästan ett decenniums erfarenhet inom maskininlärning och datavetenskap är Ke Shen alumn från Columbia University och tidigare Senior Data Scientist på Walmart Labs. Med djup, av branschen erkänd expertis inom Python, R, Java och statistik delar han väl beprövade insikter om hur man förvandlar komplexa AI-algoritmer från teori till produktionsklar arkitektur.
Topics
Puppeteer AWS LambdaServerlös webbläsarautomatiseringHeadless Chromium
Innehållsförteckning
Thunderbit · AI-agent för webbdata

Extrahera data från vilken sida som helst på 1 klick

Betrodd av över 250 000 användare
gratis plan tillgänglig
Från webbsida till kalkylark
Beskriv vad du behöver — Thunderbits AI-agent samlar in det och exporterar till Excel, Google Sheets, Airtable eller Notion. Gratis att börja med.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week