Back |Launch·

nuxt-protokit: rapid prototyping for Nuxt

Prototype a tool in one TypeScript schema and it runs in Nuxt UI, saved in the browser as you type. No backend, no database. What it is and where I use it.
Nuxtnuxt-protokitLocal-FirstOpen Source
nuxt-protokit: rapid prototyping for Nuxt

The problem

I often have ideas for small tools. A cost estimator, a tracker, an internal checklist. And the first day always went to the same stuff: a database, an API, forms. By the time it worked I wasn't even sure the idea was good anymore.

nuxt-protokit is for rapid prototyping and skips that part. You write the tool as a TypeScript schema, render it with one component, and it works. Data is saved in the browser while you type. 0.1.0 is out today.

The schema

app/schemas/resourceEstimator.ts
import { formatMoney } from '#protokit/utils/formatters'

export const resourceEstimator = definePrototype({
  key: 'resource-estimator-v1', // also the IndexedDB key
  title: 'Resource Cost Estimator',
  shortTitle: 'Estimator',
  description: 'Estimate total project cost from team size, salaries and duration.',
  icon: 'i-lucide-calculator',

  fields: {
    teamSize: { type: 'number', label: 'Team Size', default: 3, trailing: 'people' },
    avgMonthlySalary: { type: 'number', label: 'Avg. Monthly Salary', default: 4500, leading: '$' },
    projectDurationMonths: { type: 'range', label: 'Duration (months)', min: 1, max: 24, step: 1, default: 6 },
  },

  derived: {
    monthlyCost: {
      compute: ({ fields }) => fields.teamSize * fields.avgMonthlySalary,
      format: 'money',
    },
    projectTotal: {
      compute: ({ fields, derived }) => derived.monthlyCost * fields.projectDurationMonths,
      format: 'money',
    },
  },

  results: [
    {
      title: 'Cost Summary',
      stats: ({ derived }) => [
        { label: 'Monthly', value: formatMoney(derived.monthlyCost) },
        { label: 'Project Total', value: formatMoney(derived.projectTotal) },
      ],
    },
  ],
})

The page

app/pages/estimator.vue
<script setup lang="ts">
import { resourceEstimator } from '~/schemas/resourceEstimator'
</script>

<template>
  <ClientOnly>
    <ProtoTool :schema="resourceEstimator" />
  </ClientOnly>
</template>

Move a slider, the totals update. Refresh, the values are still there because they load from IndexedDB. Open it in a second tab and both tabs stay in sync over BroadcastChannel. Works offline too.

How it stores data

Every tool is a Yjs document, saved with y-indexeddb. Writes go to the browser first. If you want sync between devices, you point it at your own backend. That's five endpoints, either from a companion module or written by you. With sync off, protokit doesn't make any HTTP calls:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/ui', '@websideproject/nuxt-protokit'],
  protokit: {
    serverSync: false, // local only
  },
})

Components

There are ready-made components that use the same schema: a calendar (day, week and a scrolling month view, and you can drag tasks from a list onto it), charts (line, bar, pie, funnel, progress), detail panels, CRUD lists with edit modals, stat cards, comparison tables and timelines. There are also twelve field types, schema migrations, AES-GCM encryption, corruption recovery and per-tenant namespaces. If you want your own UI, the composables work without the components.

Where I use it

Prototypes, internal tools, demos for clients. Basically when I want to find out if an idea works before spending time on a backend.

One thing to be clear about: the data is in the browser, so the permissions only change what the UI shows. They don't protect anything. Once an idea works and needs real users and server-side rules, it should get a proper backend. I use nuxt-auto for that. Until then it's a lot less setup than Firebase, and the data is plain Yjs, so you can take it with you.

Try it

npm install @websideproject/nuxt-protokit yjs y-indexeddb @nuxt/ui tailwindcss

Tailwind doesn't scan node_modules, so add this line to the CSS file that imports Nuxt UI (the path is relative to that file):

app/assets/css/main.css
@import "tailwindcss";
@import "@nuxt/ui";

@source "../../../node_modules/@websideproject/nuxt-protokit";

The playground in the repo has 20 demos: estimators, calendars, charts, encryption, headless mode.

Next week I'll write about the local-first part in more detail: how a write is saved, what happens when two edits conflict, and what survives a crash. If you've used Yjs in production I'd really like your feedback.

Stay in the loop

Get the latest articles and updates delivered straight to your inbox.

Need a Landing Page?

Modern landing pages with optional modules (blog, docs, forms, i18n). Let's discuss your project.

Build Your MVP

Full-stack SaaS development. Expert in database design, multi-tenancy, and scalable architecture.

Deployment Help

Dockerize your backend, set up CI/CD pipelines, deploy to Cloudflare or Hetzner. Early-stage setup.

Suggest a SaaS Tool

Missing a calculator or tool? Suggest what you'd like to see on our site.