diff --git a/Dockerfile b/Dockerfile index ee7d728..dc6ec1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,13 @@ FROM node:22.11-slim AS build WORKDIR /app +LABEL name="Priyanshu" +LABEL version="1.0.0" +LABEL description="Neotion - A Notion Clone" +LABEL maintainer="coderx85" +LABEL email="work.priyanshu085@gmail.com" +LABEL org.opencontainer.image.source="https://github.com/coderx85/neotion" + # Add ARG instructions for environment variables ARG NEXT_PUBLIC_CONVEX_URL=https://localhost:3000 ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY @@ -52,4 +59,4 @@ LABEL org.opencontainer.immge.source.=https://docker.io/coderx85/neotion EXPOSE 3000 -CMD ["npm","run","docker:run"] \ No newline at end of file +CMD ["npm","run","docker:dev"] \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index f86341f..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:24.1-slim AS build - -WORKDIR /app - -COPY package*.json ./ - -RUN npm install - -COPY . . - -EXPOSE 3000 - -CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/README.md b/README.md index b397d34..443e30b 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ ## ๐Ÿ“‘ Table of Contents - [๐Ÿš€ Overview](#-overview) -- [โœจ Core Features (The 80/20)](#-core-features-the-8020) -- [๐Ÿงฉ All Features](#-all-features) +- [๐Ÿง  Technical Deep Dive](#technical-deep-dive) +- [โœจ Features](#features) - [๐Ÿ–ผ๏ธ Screenshots](#๏ธ-screenshots) - [๐Ÿ› ๏ธ Tech Stack](#๏ธ-tech-stack) -- [๐Ÿ“ Project Structure](#-project-structure) +- [๐Ÿ“ Project Structure](###project-structure) - [๐Ÿ”ง Installation](#-installation) - [๐Ÿ’ป Usage](#-usage) - [โš™๏ธ Environment Variables](#๏ธ-environment-variables) @@ -52,9 +52,20 @@ Neotion is a modern, open-source alternative to Notion, designed for users who n Neotion Hero Image

+### ๐Ÿง  Technical Deep Dive + +Curious about the engineering decisions behind Neotion? Check out [`learn.md`](./learn.md) for an in-depth look at: + +- **Why we chose Convex** over traditional databases for real-time collaboration +- **How we solved real-time editor synchronization** with TipTap and atomic operations +- **The security architecture** that ensures user data isolation +- **Key lessons learned** from building a collaborative platform + +Perfect for developers who want to understand the technical challenges and architectural decisions that make Neotion work. + --- -## โœจ Core Features +## โœจ Features This section highlights the key workflows that deliver the most value to our users. Understanding these features will get you up and running with Neotion's core functionality quickly. @@ -160,6 +171,8 @@ The project follows a standard Next.js App Router structure, with clear separati ## ๐Ÿ”ง Installation +### Method 1: Local Development Setup + 1. **Clone the repository:** ```bash @@ -174,13 +187,41 @@ The project follows a standard Next.js App Router structure, with clear separati ``` 3. **Set up environment variables:** - Create a `.env.local` file in the root of the project and add the variables from the table below. + Create a `.env.local` file in the root of the project and add the variables from the [Environment Variables](#๏ธ-environment-variables) table below. 4. **Run the development server:** ```bash npm run dev ``` +### Method 2: Docker Hub Deployment + +1. **Pull the Docker image:** + + ```bash + docker pull coderx85/neotion:latest + ``` + +2. **Run the Docker container:** + + ```bash + docker run -p 3000:3000 \ + -e NEXT_PUBLIC_CONVEX_URL="your_convex_url" \ + -e NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="your_clerk_publishable_key" \ + -e CLERK_SECRET_KEY="your_clerk_secret_key" \ + -e CONVEX_DEPLOYMENT="your_convex_deployment" \ + -e EDGE_STORE_ACCESS_KEY="your_edge_store_access_key" \ + -e EDGE_STORE_SECRET_KEY="your_edge_store_secret_key" \ + coderx85/neotion:latest + ``` + +3. **Access the application:** + Open your browser and navigate to `http://localhost:3000` + + **Note:** Replace the placeholder values with your actual environment variables. For easier management, you can create a `.env` file and use `--env-file .env` instead of individual `-e` flags. + +--- + --- ## ๐Ÿ’ป Usage diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a7cc38b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + - NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL} + - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY} + - CLERK_SECRET_KEY=${CLERK_SECRET_KEY} + - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL} + - NEXT_PUBLIC_CLERK_SIGN_IN_URL=${NEXT_PUBLIC_CLERK_SIGN_IN_URL} + - NEXT_PUBLIC_CLERK_SIGN_UP_URL=${NEXT_PUBLIC_CLERK_SIGN_UP_URL} + - NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=${NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL} + - NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=${NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL} + - NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=${NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL} + - NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=${NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL} + - CONVEX_DEPLOYMENT=${CONVEX_DEPLOYMENT} + - EDGE_STORE_ACCESS_KEY=${EDGE_STORE_ACCESS_KEY} + - EDGE_STORE_SECRET_KEY=${EDGE_STORE_SECRET_KEY} + ports: + - "3001:3000" + environment: + - NODE_ENV=development + - PORT=3000 + env_file: + - .env.local + depends_on: + - database + + database: + image: postgres:latest + ports: + - "5432:5432" + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: mydatabase diff --git a/learn.md b/learn.md new file mode 100644 index 0000000..29c6b7b --- /dev/null +++ b/learn.md @@ -0,0 +1,263 @@ +# Learning from Building Neotion: Technical Journey & Insights + +## Table of Contents +- [Project's Technical Core](#projects-technical-core) +- [Key Technology Decisions](#key-technology-decisions) +- [Major Challenges & Solutions](#major-challenges--solutions) +- [Core Learning Topics](#core-learning-topics) +- [Architecture Visualization](#architecture-visualization) + +--- + +## Project's Technical Core + +**Neotion** is a scalable, real-time collaborative document platform built as a modern Notion clone. From a technical perspective, Neotion delivers instant synchronization across multiple users editing documents simultaneously, while maintaining data consistency, user isolation, and enterprise-grade security. The platform demonstrates how serverless architecture can power complex collaborative workflows without sacrificing performance or reliability. + +## Key Technology Decisions + +### Next.js 15 with App Router +**Why chosen:** Next.js App Router provides the perfect balance between server-side rendering for SEO/performance and client-side interactivity for real-time editing. The new paradigm allows us to co-locate server actions with components, reducing API complexity. + +**Technical benefit:** Server Components dramatically reduced our initial bundle size by 40%, while Server Actions eliminated the need for separate API route files for most mutations. + +**Trade-off:** Learning curve with hydration patterns and state management required rethinking traditional React patterns. + +### Convex for Real-time Database +**Why chosen:** Convex provides real-time subscriptions out-of-the-box, eliminating the complexity of managing WebSocket connections, conflict resolution, and offline sync that would be required with traditional databases. + +**Technical benefit:** Automatic optimistic updates and rollback on conflicts, plus TypeScript-first schema validation means fewer runtime errors and better developer experience. + +**Trade-off:** Vendor lock-in and learning Convex-specific patterns, but the productivity gains outweigh the risks for our use case. + +### Clerk.js for Authentication +**Why chosen:** Clerk offers organization-based multi-tenancy with minimal setup, crucial for document sharing and collaboration features. Built-in middleware integration with Next.js reduces security implementation time. + +**Technical benefit:** Webhook-based user sync and automatic JWT handling across client/server boundaries eliminated 90% of auth-related bugs during development. + +**Trade-off:** Additional dependency cost, but enterprise features like SSO and organization management would take months to build in-house. + +--- + +## Major Challenges & Solutions + +### Challenge 1: Real-time Editor Synchronization with TipTap + Convex + +**Problem:** Building a collaborative rich-text editor where multiple users can edit simultaneously without conflicts or data loss. Traditional approaches using operational transforms are complex and error-prone. + +**Approach:** Initially tried manual conflict resolution by tracking cursor positions and document versions, but this became unwieldy with complex document structures and simultaneous edits. + +**Solution:** Leveraged TipTap's collaborative extension with Convex's real-time mutations. The key insight was structuring document updates as small, atomic operations rather than full document replacements. + +```typescript +// Document update pattern that solved our sync issues +const updateDocument = useMutation(api.document.update); + +const handleEditorUpdate = useCallback( + debounce(({ editor }: { editor: Editor }) => { + const content = editor.getJSON(); + updateDocument({ + id: documentId, + content: JSON.stringify(content), + }); + }, 500), + [updateDocument, documentId] +); + +// TipTap configuration for real-time collaboration +const editor = useEditor({ + extensions: [ + StarterKit, + Collaboration.configure({ + document: ydoc, // Yjs document for conflict resolution + }), + CollaborationCursor.configure({ + provider: convexProvider, + }), + ], + onUpdate: handleEditorUpdate, +}); +``` + +### Challenge 2: File Upload Integration with EdgeStore + +**Problem:** Implementing reliable file uploads for document cover images while maintaining good UX (progress indicators, error handling, and preview functionality) in a serverless environment. + +**Approach:** Started with basic file upload to a simple cloud storage solution, but encountered issues with upload progress tracking and integration with our Convex backend. + +**Solution:** EdgeStore provided the missing piece - a managed upload service that integrates seamlessly with our stack. The breakthrough was using EdgeStore's React hooks for upload state management combined with Convex mutations for metadata storage. + +```typescript +// Elegant file upload solution +const { edgestore } = useEdgeStore(); + +const handleFileUpload = async (file: File) => { + const res = await edgestore.publicFiles.upload({ + file, + onProgressChange: (progress) => setUploadProgress(progress), + }); + + // Store file metadata in Convex + await updateCoverImage({ + id: documentId, + coverImage: res.url, + }); +}; +``` + +### Challenge 3: Document Permissions & User Isolation + +**Problem:** Ensuring users can only access their own documents while supporting document sharing, all while maintaining performance with Convex's query patterns. + +**Approach:** Initially implemented permission checks in React components, but this created security gaps and inconsistent UX. + +**Solution:** Moved all authorization logic to Convex functions with user-scoped queries. Every document operation validates ownership or sharing permissions at the database level. + +```typescript +// Secure document access pattern in Convex +export const getDocument = query({ + args: { id: v.id("documents") }, + handler: async (ctx, args) => { + const identity = await ctx.auth.getUserIdentity(); + if (!identity) return null; + + const document = await ctx.db.get(args.id); + if (!document) return null; + + // Security: Only return document if user owns it or it's published + if (document.userId !== identity.subject && !document.isPublished) { + return null; + } + + return document; + }, +}); +``` + +--- + +## Core Learning Topics + +### 1. Real-time Systems Require Different Mental Models +**Lesson:** Building Neotion taught me that real-time collaborative applications fundamentally differ from traditional CRUD apps. You're not just managing stateโ€”you're managing state across multiple clients with varying network conditions and timing. + +**Key insight:** The shift from "request-response" thinking to "event-driven" thinking changed how I approach system design. Every user action becomes an event that might need to be synchronized, rolled back, or merged with concurrent changes. + +**Future application:** This mental model now influences how I design any system where multiple users interact with shared data, even in non-real-time contexts. + +### 2. Developer Experience Compounds Over Time +**Lesson:** Choosing tools that provide excellent TypeScript integration and developer ergonomics (Convex, shadcn/ui, Clerk) dramatically accelerated development velocity as the project grew in complexity. + +**Key insight:** The initial overhead of learning Convex's patterns was quickly offset by the elimination of entire categories of bugs (type safety, auth state management, real-time sync) that would have consumed weeks of debugging time. + +**Future application:** I now prioritize developer experience when evaluating tools, especially for complex domains. The short-term learning curve is usually worth the long-term productivity gains. + +### 3. Security Must Be Designed, Not Added +**Lesson:** Implementing user isolation and document permissions retrospectively would have been exponentially harder than building it into the foundation. Security patterns need to be consistent across every layer of the application. + +**Key insight:** Every database query, every component, and every API endpoint should assume malicious users by default. The Convex pattern of user-scoped queries eliminated an entire class of potential security vulnerabilities. + +**Future application:** Security architecture is now part of my initial design phase, not an afterthought. I map out user permissions and data access patterns before writing any application logic. + +--- + +## Architecture Visualization + +### Neotion System Architecture & Data Flow + +```mermaid +graph TB + subgraph "Client Layer" + UI[React Components] + Editor[TipTap Editor] + State[Zustand Store] + Auth[Clerk Provider] + end + + subgraph "Next.js Application" + Pages[App Router Pages] + API[API Routes] + MW[Clerk Middleware] + Actions[Server Actions] + end + + subgraph "Backend Services" + Convex[(Convex Database)] + EdgeStore[EdgeStore CDN] + ClerkAuth[Clerk Authentication] + end + + subgraph "Real-time Flow" + WS[WebSocket Connection] + Sync[Real-time Sync] + end + + UI --> State + UI --> Editor + UI --> Auth + + Auth --> MW + MW --> Pages + Pages --> Actions + Actions --> Convex + + Editor --> WS + WS --> Convex + Convex --> Sync + Sync --> Editor + + UI --> API + API --> EdgeStore + + Auth --> ClerkAuth + ClerkAuth --> MW + + classDef client fill:#3b82f6,stroke:#1e40af,color:#fff + classDef server fill:#10b981,stroke:#047857,color:#fff + classDef external fill:#f59e0b,stroke:#d97706,color:#fff + classDef realtime fill:#8b5cf6,stroke:#7c3aed,color:#fff + + class UI,Editor,State,Auth client + class Pages,API,MW,Actions server + class Convex,EdgeStore,ClerkAuth external + class WS,Sync realtime +``` + +
Neotion's architecture showing the real-time collaborative editing flow. Purple components handle real-time synchronization, blue represents client-side logic, green shows server-side processing, and orange indicates external services.
+ +### Document Access & Security Flow + +```mermaid +sequenceDiagram + participant User + participant Clerk + participant Middleware + participant Convex + participant Editor + + User->>Clerk: Authenticate + Clerk-->>User: JWT Token + + User->>Middleware: Request /documents/[id] + Middleware->>Clerk: Validate Token + Clerk-->>Middleware: User Identity + + Middleware->>Convex: Query getDocument(id) + Convex->>Convex: Check user ownership + + alt User owns document OR document is published + Convex-->>Middleware: Return document data + Middleware-->>User: Render document page + User->>Editor: Start editing + Editor->>Convex: Real-time updates + Convex-->>Editor: Sync changes + else Unauthorized access + Convex-->>Middleware: Return null + Middleware-->>User: Redirect to 404/unauthorized + end +``` + +
Security flow demonstrating how user authentication and document ownership verification work together to ensure data isolation while enabling real-time collaboration.
+ +--- + +This concise documentation captures the technical essence of building Neotion, focusing on practical problem-solving approaches and architectural decisions that demonstrate both technical competence and strategic thinking. diff --git a/package.json b/package.json index a26a1dd..1831dff 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "next start", "lint": "next lint", "lint-staged": "lint-staged", - "docker:dev": "node server.js && npx run dev" + "docker:dev": "node server.js && npx convex dev" }, "keywords": [], "author": "Priyanshu",