Skip to content

Notion Clone with Next.js 13(App Router) tailwindcss, Shadcn UI, Edgestore for files Upload, convex for realtime databse

Notifications You must be signed in to change notification settings

Awais512/notion-clone

Repository files navigation

πŸ“ Notion-Style Document Platform

A real-time collaborative document editing platform with a hierarchical document structure and rich media support.

Next JS React Convex TypeScript

✨ Key Features

πŸ“„ Document Management

  • Real-time Collaboration

    • Live updates using Convex
    • Multi-user editing
    • Presence indicators
    • Conflict resolution
  • Document Structure

    • Infinite nested documents
    • Hierarchical navigation
    • Document icons
    • Cover images
  • Document Recovery

    • Trash can functionality
    • Soft delete
    • File recovery
    • Version history

πŸ“ Rich Text Editor

  • Notion-style Editor
    • Block-based editing
    • Rich text formatting
    • Code blocks
    • Tables
    • Lists
    • Media embeds

🎨 User Interface

  • Responsive Design

    • Mobile-first approach
    • Adaptive layouts
    • Touch-friendly interactions
    • Collapsible sidebar
  • Appearance

    • Light/Dark mode
    • Custom document icons
    • Cover image support
    • Responsive images

πŸ—„οΈ Database Schema

// convex/schema.ts
import { defineSchema, defineTable } from 'convex/schema';
import { v } from 'convex/values';

export default defineSchema({
  documents: defineTable({
    title: v.string(),
    content: v.optional(v.string()),
    coverImage: v.optional(v.string()),
    icon: v.optional(v.string()),
    userId: v.string(),
    parentDocument: v.optional(v.id('documents')),
    isArchived: v.boolean(),
    isPublished: v.boolean(),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_user', ['userId'])
    .index('by_parent', ['parentDocument'])
    .index('by_user_parent', ['userId', 'parentDocument']),
  
  files: defineTable({
    documentId: v.id('documents'),
    userId: v.string(),
    url: v.string(),
    type: v.string(),
    size: v.number(),
    createdAt: v.number(),
  })
    .index('by_document', ['documentId'])
    .index('by_user', ['userId']),
});

πŸ’Ž Core Features Implementation

πŸ“ Document Editor

// components/Editor.tsx
import { BlockNoteEditor } from '@blocknote/react';
import { useConvex } from 'convex/react';

export function Editor({ 
  documentId,
  initialContent,
}: { 
  documentId: string;
  initialContent?: string;
}) {
  const convex = useConvex();
  const [editor] = useState(() => 
    new BlockNoteEditor({
      initialContent: initialContent 
        ? JSON.parse(initialContent)
        : undefined,
    })
  );

  const debouncedUpdate = useMemo(
    () => debounce(async (content: string) => {
      await convex.mutation('documents:update', {
        id: documentId,
        content,
        updatedAt: Date.now(),
      });
    }, 500),
    [documentId]
  );

  return (
    <BlockNoteView
      editor={editor}
      onChange={content => {
        debouncedUpdate(JSON.stringify(content));
      }}
    />
  );
}

πŸ“š Document Hierarchy

// hooks/useDocuments.ts
export function useDocuments(parentId?: string) {
  const documents = useQuery('documents:list', {
    parentDocument: parentId,
    isArchived: false,
  });

  const documentsWithChildren = useMemo(() => {
    return documents?.map(doc => ({
      ...doc,
      children: documents.filter(
        child => child.parentDocument === doc._id
      ),
    }));
  }, [documents]);

  return documentsWithChildren;
}

// components/DocumentTree.tsx
export function DocumentTree({ 
  documents,
  level = 0 
}: {
  documents: Document[];
  level?: number;
}) {
  return (
    <div style={{ paddingLeft: level * 12 }}>
      {documents.map(document => (
        <div key={document._id}>
          <DocumentItem document={document} />
          {document.children?.length > 0 && (
            <DocumentTree
              documents={document.children}
              level={level + 1}
            />
          )}
        </div>
      ))}
    </div>
  );
}

πŸ—‘οΈ Trash Management

// hooks/useTrash.ts
export function useTrash() {
  const convex = useConvex();

  const moveToTrash = async (documentId: string) => {
    await convex.mutation('documents:archive', {
      id: documentId,
      isArchived: true,
      archivedAt: Date.now(),
    });
  };

  const restore = async (documentId: string) => {
    await convex.mutation('documents:archive', {
      id: documentId,
      isArchived: false,
      archivedAt: null,
    });
  };

  const deletePermanently = async (documentId: string) => {
    await convex.mutation('documents:delete', {
      id: documentId,
    });
  };

  return {
    moveToTrash,
    restore,
    deletePermanently,
  };
}

🌐 Document Publishing

// hooks/usePublish.ts
export function usePublish() {
  const convex = useConvex();

  const publishDocument = async (documentId: string) => {
    await convex.mutation('documents:publish', {
      id: documentId,
      isPublished: true,
      publishedAt: Date.now(),
    });
  };

  const unpublishDocument = async (documentId: string) => {
    await convex.mutation('documents:publish', {
      id: documentId,
      isPublished: false,
      publishedAt: null,
    });
  };

  return {
    publishDocument,
    unpublishDocument,
  };
}

🎨 Theme Switching

// hooks/useTheme.ts
export function useTheme() {
  const [theme, setTheme] = useState<'light' | 'dark'>(
    () => (localStorage.getItem('theme') as 'light' | 'dark') || 'light'
  );

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.classList.toggle('dark');
  };

  useEffect(() => {
    document.documentElement.classList.toggle(
      'dark', 
      theme === 'dark'
    );
  }, []);

  return {
    theme,
    toggleTheme,
  };
}

πŸš€ Getting Started

Prerequisites

  • Node.js 18+
  • Convex account
  • Authentication provider account

Installation

  1. Clone the repository
git clone https://github.com/yourusername/notion-clone.git
cd notion-clone
  1. Install dependencies
pnpm install
  1. Set up environment variables
# .env.local
CONVEX_DEPLOYMENT=
NEXT_PUBLIC_CONVEX_URL=
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
  1. Initialize Convex
npx convex dev
  1. Start development server
pnpm dev

⚑ Performance Optimizations

  • Real-time updates batching
  • Image optimization
  • Code splitting
  • Tree shaking
  • Lazy loading

πŸ”’ Security Features

  • Authentication
  • Document permissions
  • API route protection
  • Input validation
  • File upload restrictions

πŸš€ Deployment

  1. Deploy Convex
npx convex deploy
  1. Configure Vercel
vercel env pull
  1. Deploy
vercel deploy

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

πŸ“„ License

This project is licensed under the MIT License.


πŸ™ Acknowledgments


Built with πŸ“ by Awais Raza

About

Notion Clone with Next.js 13(App Router) tailwindcss, Shadcn UI, Edgestore for files Upload, convex for realtime databse

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published