Case Study·SaaS Platform·Print-on-demand

Live at pengine.io

We built Pengine, a Shopify print-on-demand platform, in Vue 3 & Nuxt: design editor, bulk publishing, orders and billing.

Merchants connect a Shopify store, pick blank products, place their artwork and publish, then send orders to production. Our team built the seller app with its fabric.js design editor and bulk tools, the internal admin with a mockup editor and pricing engine, and the Nuxt 4 marketing site.

  • Vue 3.5
  • Nuxt 3 & 4
  • Pinia
  • PrimeVue 4
  • Tailwind 4
  • fabric.js
  • Typesense
  • Socket.IO
  • Stripe
Pengine seller app product editor: print method selection, artwork upload and a long-sleeve shirt with the print area marked
Print-safe placement Artwork is clamped to the printable area while you zoom and drag
Print specs Physical size and effective DPI shown live
Every color Previews for each garment color, rendered in the browser
Since 2023
In continuous development
Vue 3 + Vite first, then rebuilt in Nuxt
3
Nuxt apps
seller app · admin · marketing site
860+
Vue components
112K lines of .vue across the apps
Shopify
Published on the App Store
one-click store connection

Print-on-demand without the busywork

Pengine is a print-on-demand and dropshipping platform for Shopify merchants. Sellers connect their store, choose blank products, design them, set prices and publish, and Pengine takes care of production when orders come in.

The product lives or dies on the seller workflow: how fast a merchant can turn artwork into live products, keep hundreds of them in sync, and get orders into production. Behind it, Pengine's team needs internal tools to build mockup templates, manage a catalog of blanks, and control pricing.

We've worked on Pengine since 2023, through two generations of the product. This page shows the live product and describes our frontend engineering without publishing source code.

Product
Pengine: Shopify-first print-on-demand and dropshipping platform
Our role
Seller app, internal admin and marketing site, across two generations
Seller app
Nuxt 3 SPA · PrimeVue 4 (unstyled) · Tailwind 4 · Pinia · fabric.js
Admin
Nuxt 3 SPA · PrimeVue 4 · Pinia · fabric.js · TinyMCE
Website
Nuxt 4 SSR · Tailwind 4 design tokens · Sanity · @nuxtjs/seo
Integrations
Shopify · Stripe · Typesense · Socket.IO · FulfillEngine · Sanity
Status
Live at pengine.io and on the Shopify App Store

Two generations, one product, no big-bang rewrite

Pengine shipped early on a simple stack, then moved to Nuxt one app at a time while the old version kept running. It's the approach we recommend to any team with a product that has outgrown its first build. Dates come from the project's git history.

Pengine timelineMar 2023 → 2026
MAR 2023 Vue 3 + Vite apps
DEC 2024 Admin rebuilt in Nuxt
JUN 2025 Seller app rebuilt in Nuxt
AUG 2025 Design editor & bulk create
JUN 2026 Versioned releases
JUN 2026 Nuxt 4 marketing site
01

Ship the first generation

2023 – 2025
On Pengine

Pengine started as two Vue 3 + Vite single-page apps, one for sellers and one for the internal team. Shopify store connection followed in late 2023. That version carried the business for two years and taught us exactly where the product needed to go.

What you get
  • A working product in market early
  • Real usage to learn from before a bigger investment
  • A clear list of what the next version must fix
02

Rebuild the admin in Nuxt

Dec 2024 – Jul 2025
On Pengine

The internal admin was rebuilt first, in Nuxt, while the old one kept running. It gained the tools the team needed to scale: instant search, a mockup template editor, a full pricing engine and an audit trail.

  1. Typesense search across catalog, orders and sellers
  2. fabric.js mockup template editor
  3. Audit trail with change diffs on pricing screens
  4. Production pipeline for the new admin
What you get
  • Replatforming without a big-bang cutover
  • Internal tools first, where risk is lowest
  • The old system running until the new one is proven
03

Rebuild the seller app

Jun – Dec 2025
On Pengine

Then the merchant-facing app, also in Nuxt, rebuilt around the workflows sellers use most: designing products, publishing in bulk, and getting orders into production.

  1. Typesense-powered catalog and orders
  2. Stripe payment methods, design editor, bulk create with saved progress
  3. Live product status over Socket.IO
What you get
  • Workflows rebuilt around real seller behaviour
  • Clean, review-ready pull requests
  • Async daily updates and a weekly progress review
04

Run old and new side by side

2025
On Pengine

For months both generations ran at once. The new seller app could hand a signed-in user over to the old dashboard for anything not yet rebuilt, so merchants could keep working during the move.

What you get
  • A migration plan merchants don't notice
  • Feature-by-feature cutover
  • No frozen roadmap while the rebuild happens
05

Release like a product company

Jun 2026
On Pengine

The seller app moved to versioned releases: v1.0.0 shipped in June 2026, and from then on a version tag builds the app and deploys it to S3 behind CloudFront. Dev, staging and production each have their own domain and pipeline.

What you get
  • Versioned, repeatable releases
  • Separate dev, staging and production
  • Cache rules tuned for instant updates
06

A marketing site that ranks

Jun – Sep 2026
On Pengine

Finally, a new marketing site on Nuxt 4: server-rendered, with a design system driven by Figma tokens, a Sanity blog the marketing team runs, structured data, generated OG images and a sitemap fed by the CMS.

What you get
  • SEO built in, not bolted on
  • A CMS the marketing team owns
  • A design system that matches the product

Eight problems behind a smooth seller workflow

Print-on-demand looks like "upload a design, sell a shirt". Underneath are canvas geometry, bulk jobs, realtime status, search and pricing. These are the problems we solved on Pengine.

01

Keeping artwork inside the printable area

fabric.jsCanvasPrint
Problem
A design must never extend past what the printer can actually print, even while the seller zooms, pans, scales and drags it around the editor.
How we solved it
The printable region is a clip path that is rebuilt whenever the artwork moves or the viewport changes. Scaling and moving are clamped in real time, so the design can fill the print area but never leave it.

Where this applies to you Any editor where user content has to respect physical or business limits: print, signage, packaging, templates.

Engine
fabric.js inside a Nuxt app
Clipping
Print region rebuilt on move, scale and zoom
Clamping
Scale and position limited live
Controls
Zoom, pan, align, flip, keyboard nudge
02

Turning canvas pixels into print specs

PrintGeometry
Problem
Screen pixels mean nothing to a printer. Production needs physical size, position and resolution, and sellers need to know before ordering if their file will print blurry.
How we solved it
Positions are measured against the print region's real size in inches. The editor shows physical width and height and effective DPI live, and saves size plus offsets from the print area's center and top for production.

Where this applies to you Products where on-screen design turns into a physical output: print, embroidery, engraving, signage.

Units
Inches from the print region's real size
Quality
Effective DPI shown live
Saved
Size and offsets per print location
Per product
Front, back and other locations
03

Photoshop-style mockup templates, built in the browser

fabric.jsAdminCompositing
Problem
Realistic garment mockups are stacks of photo layers (background, garment, color, design, shadows, highlights), each with its own blend mode and adjustments, for every color of every product.
How we solved it
The admin includes a layer editor: per print location, a print region plus a stack of layers with blend modes, hue, saturation, brightness, contrast and opacity. Templates are saved independent of zoom and offset, per color, ready for rendering.

Where this applies to you Internal tools that let your team create visual content without designers or developers in the loop.

Layers
6 defaults plus custom layers
Blend modes
10, with HSL and contrast filters
Scope
Per print location, per color
Output
Saved as JSON for server rendering
04

Slow render jobs with live status, no polling

Socket.IORealtime
Problem
Rendering mockups for every color takes time. Users need to see progress and results as they finish, without refreshing or hammering the API.
How we solved it
Starting a render returns immediately. A Socket.IO channel then pushes status updates, and the UI patches the matching color card or product row in place, in both the admin and the seller app.

Where this applies to you Any product with long-running jobs: exports, imports, AI generation, video processing, reports.

Admin
Render status per color, pushed live
Seller
Product status and thumbnail, pushed live
Transport
Socket.IO namespaces
Polling
None
05

A folder of artwork becomes a catalog of products

Bulk operationsIndexedDB
Problem
Sellers launch collections with hundreds of designs. Placing each one by hand doesn't scale, and a job that size has to survive pauses, reloads and switching stores.
How we solved it
Sellers drop a folder: each subfolder becomes a product, and file names map designs to front or back. Files upload in parallel batches, each design is auto-fitted to the print area, and products are created one by one with pause and resume. Progress is saved to IndexedDB after every product.

Where this applies to you Bulk imports and batch jobs that users start, leave and come back to.

Input
Drag-and-drop folders, recursively
Mapping
File names to print locations
Uploads
Parallel batches with auto-fit
Resilience
Pause, resume, saved per store
06

Faceted search that doesn't hide your options

TypesenseSearchComposables
Problem
With ordinary faceting, choosing "Brand: X" hides every other brand, so users can't compare or add a second choice. Every catalog, order and artwork list needed fast, filterable search.
How we solved it
One shared composable runs a single multi-search per change: the main filtered query plus a count-only query per selected field that leaves out that field's own filter. Counts stay correct for every option, filter state lives in the URL, and each user gets a scoped search key.

Where this applies to you Catalogs, marketplaces, admin tables: anywhere users filter large data sets.

Engine
Typesense with per-user scoped keys
Pattern
Disjunctive faceting in one request
Reuse
One composable, 7 collections (seller)
State
Filters kept in the URL
07

Instant previews for every color

fabric.jsPerformance
Problem
Sellers want to see their design on every garment color they select before saving, and waiting on a server round trip per color is slow.
How we solved it
The editor clones the design, swaps in each color's garment photo, renders it on an off-screen canvas and exports a preview, for all selected colors in parallel, entirely in the browser.

Where this applies to you Product customisers and configurators where instant visual feedback drives conversion.

Where
In the browser, off-screen canvas
Parallel
All selected colors at once
Server
No round trip for previews
Final
Status and thumbnail pushed by the server
08

A pricing engine with built-in coverage checks

PricingAdminValidation
Problem
Each product's price is built from components (blank, printing, surcharges) that differ by print method and location, plus shipping by zone and segment-specific overrides. One missing combination means a wrong price at checkout.
How we solved it
The admin models prices as groups of components with markups and discounts, shipping plans by tier and zone, price sheets and time-boxed seller segments. On save it checks every print method × location combination and flags anything missing.

Where this applies to you Configurable pricing, quoting or rating engines where mistakes cost real money.

Model
Component groups, markups, discounts
Shipping
Tiers → zones → areas
Segments
Time-boxed seller overrides
Checks
Missing combinations flagged on save

Three Nuxt apps around one API

Each app gets the hosting and rendering it needs: static builds for the seller app (on S3 and CloudFront) and the admin, server rendering on Vercel for the marketing site. Search, realtime status, payments and Shopify plug in through proven services.

Pengine architecture: the Nuxt seller app, Nuxt admin and Nuxt 4 marketing site connect to the Pengine REST API, with Typesense search, Socket.IO status updates, Stripe, Shopify, FulfillEngine and Sanity.APPS WE BUILTSeller appNuxt 3 SPA · S3 + CloudFrontdesign editor · bulk createorders · billing · teamAdminNuxt 3 SPA · PrimeVue 4mockup editor · catalogpricing engine · audit trailMarketing siteNuxt 4 SSR · Vercelcatalog pages · blogschema.org · sitemapPLATFORMPengine APIREST · /v1products · orders · invoicesrender jobs · pricingbackend not covered on this pageSERVICESShopifystore connection · product syncTypesensesearch with per-user keysSocket.IOrender and product statusStripesaved cards · invoice paymentsFulfillEngineblank product importSanityblog content
Built by our teamPlatform and services

What this project shows about our Vue work

Static SPAs, fast to host

The seller app and admin are client-rendered and statically generated; the seller app is served from S3 and CloudFront with immutable assets and revalidated HTML.

nuxi generate · CloudFront
Nuxt 4 SSR site

The marketing site renders on the server with schema.org data, a CMS-fed sitemap, generated OG images and an llms.txt route.

@nuxtjs/seo · Vercel
fabric.js in Vue

Two canvas editors living inside Nuxt apps: the seller's design editor and the admin's mockup template editor.

fabric.js · Pinia
Design systems

PrimeVue 4 unstyled with Tailwind 4 in the seller app, PrimeVue Aura presets with 11 color themes in the admin, and Figma tokens in the site.

PrimeVue 4 · Tailwind 4
State with Pinia

Stores per domain (editor, bulk jobs, orders, billing, pricing) with composables for search, permissions and storage.

stores/* · composables/*
Search composables

A reusable Typesense multi-filter composable powering catalog, orders, products and artwork lists with URL-synced filters.

useTypesenseMultiFilter
Permission-aware UI

Store owners invite staff with permissions across six areas; menus, routes and actions adapt to what each user can do.

usePermissions · middleware
Stripe Elements

Cards saved off-session with SetupIntents and 3-D Secure, so card data never touches Pengine's servers; invoices are charged later.

Payment Element · SetupIntent
Realtime

Socket.IO listeners patch render and product status into the UI as jobs finish.

socket.io-client
Local persistence

Bulk jobs saved to IndexedDB per user and per store, so a reload never loses progress.

localforage
Forms

vee-validate with yup schemas across both apps, including conditional pricing validation.

vee-validate · yup
Headless CMS

A Sanity blog rendered from Portable Text on the server, with a private dataset and server-only token.

Sanity · Nitro

Projects where this experience pays off

A Shopify app or e-commerce integration

Store connection, product sync, bulk publishing and order flow between your platform and the merchant's store.

Published Shopify app · bulk connect · order production

A design editor or product customiser

Canvas editing with real-world constraints, live previews and output a machine or partner can use.

fabric.js editors · print-safe placement · DPI

Bulk operations at scale

Imports, batch creation and long-running jobs users can pause, leave and resume.

Folder → products · IndexedDB progress · live status

Complex pricing or quoting

Component-based pricing, shipping zones, segments and overrides, with validation that catches gaps.

Price sheets · seller segments · coverage checks

A marketing site that has to rank

Server-rendered Nuxt with structured data, a headless CMS and a design system that matches the product.

Nuxt 4 · Sanity · @nuxtjs/seo

A Vue SPA that needs a rebuild

Moving from an aging Vite SPA to Nuxt without stopping the business or stranding users.

Pengine: Vue 3 + Vite → Nuxt, side by side

Questions teams ask us

What did VueSquad build for Pengine?

Our engineers built Pengine's seller app, internal admin and marketing site, and the first-generation Vue apps before them. This page shows the live product and describes our frontend engineering; the backend API isn't covered here.

Can you build Shopify apps and integrations?

Yes. Pengine connects to merchants' Shopify stores through its published Shopify app, publishes products in bulk, maps existing Shopify products onto templates, and brings orders back for production.

Can you migrate our Vue SPA to Nuxt?

Yes. Pengine moved from Vue 3 + Vite single-page apps to Nuxt one app at a time, with both generations running side by side so users could keep working throughout. Migrations and long-term maintenance are core VueSquad work.

Do you work with canvas and image editors?

Yes. Pengine has two fabric.js editors inside Nuxt: a seller design editor with print-safe placement and live DPI, and an admin mockup editor with layered compositing and blend modes.

Nuxt 3 or Nuxt 4?

Both. Pengine's seller app and admin run on Nuxt 3, and its marketing site runs on Nuxt 4. We can start a new project on Nuxt 4 or work in an existing Nuxt 3 codebase.

How do we start?

Send us a short note on the contact page about what you're building and where you're stuck. We'll set up a call to go through your product and codebase.

Get started

Building an e-commerce or Shopify product in Vue?

Talk to the engineers who built Pengine's design editor, bulk tools, admin and marketing site. Tell us what you're building and where it's stuck.

Matched in 24 hours·48 hours free on every plan·No long-term contract