Back to Portfolio
Web

Doc2Kindle

Combine Google Docs into a single Kindle-ready EPUB

The Problem

Writers drafting books in Google Docs face a painful workflow to preview their work on a Kindle. They must manually export each chapter, combine them in a third-party tool, convert to EPUB, and deal with formatting loss at every step. There’s no tool that goes from “select your Google Docs” to “here’s your EPUB” in one step.

Amazon’s Send to Kindle service accepts EPUB files, but creating a properly formatted EPUB from multiple Google Docs — with chapter ordering, preserved formatting, and Kindle-optimized typography — requires technical knowledge most writers don’t have.

Visual Demo

“Screenshots coming soon — the app features Google Drive Picker integration, drag-and-drop chapter reordering, and one-click EPUB download.”

The Solution

The app connects to Google Drive through OAuth and the Google Picker API, which is loaded dynamically at runtime to avoid blocking the initial page load. When a user authenticates, the OAuth implicit grant flow returns a token in the URL hash. The app reads it immediately, clears the hash via history.replaceState to keep URLs clean and prevent token leakage through browser history, and holds the token only in React state for the duration of the session. Documents are exported from Google Drive as HTML rather than plain text, which preserves all structural markup — headings, bold, italic, paragraphs — that would otherwise be flattened into a meaningless wall of text.

The EPUB assembly pipeline is built from scratch because existing npm packages like epub-gen broke in Vercel’s serverless runtime. The assembler constructs ZIP binary format manually: local file headers, CRC-32 checksums computed via a pure-JS lookup table using polynomial 0xEDB88320, and a central directory record tying it all together. The mimetype entry is stored uncompressed as the first file in the archive per EPUB 3.0 spec requirements. Each chapter becomes an XHTML file with Kindle-targeted CSS — Georgia serif, 1.6 line-height — designed to render cleanly on e-ink displays. The entire pipeline runs in serverless function memory with zero file storage, meaning no cleanup and no storage costs.

Chapter ordering uses HTML5 drag-and-drop on desktop and up/down arrow buttons on mobile for full device coverage. Dragged cards receive an opacity-50 treatment while drop targets highlight with a green border, giving clear visual feedback throughout the interaction. Reordering is handled through immutable splice-based updates in React state, keeping the operation predictable and debuggable. For beta access, the app leverages Google’s own OAuth “Testing” mode as a gate — only whitelisted emails can sign in. A Resend-powered email form on the landing page lets users request access, with the subject line automatically tagged [Gmail] or [Non-Gmail] so requests can be triaged at a glance without building a custom auth system.

Architecture

Next.js app → Google Drive API (OAuth + Picker) → Server-side EPUB assembly → Resend (beta access emails)

Tech Stack

Next.js 14 React 18 TypeScript Google Drive API Google Picker API Tailwind CSS Resend Vercel

By the Numbers

Hand-rolled EPUB 3.0 assembler with pure-JS CRC-32

Zero file storage — entire pipeline runs in serverless function memory

Deploy-anywhere: Vercel, Railway, Render, Docker

Key Technical Decisions

HTML export over plain text

Documents are exported from Google Drive as HTML, not plain text. This preserves structural markup (headings, bold, paragraphs) that gets embedded directly into the EPUB chapter XHTML.

Hand-rolled ZIP/EPUB over npm library

epub-gen and similar packages had compatibility issues with Vercel's serverless environment. The hand-rolled approach constructs ZIP binary format manually — local file headers, CRC-32 checksums, central directory — with the mimetype file stored uncompressed per EPUB 3.0 spec.

Google OAuth Testing mode as beta gate

While the OAuth app is in Testing mode, only whitelisted emails can sign in. A Resend-powered email form sends access requests tagged [Gmail] vs [Non-Gmail] for quick triage. No custom auth system needed.