PrivyDrop is a P2P file/text sharing tool based on WebRTC, designed to provide a secure, private, and efficient online sharing solution. The core objective of the frontend architecture is to build a high-performance, maintainable, and scalable modern web application, adhering to Next.js best practices.
In a recent refactor, we established a design philosophy centered on "Separation of Concerns" and "Logical Cohesion":
- UI and Logic Separation: Views (Components) should remain as "pure" as possible, responsible only for rendering the UI and responding to user interactions. All complex business logic, state management, and side effects should be extracted from components.
- Hooks as the Core of Business Logic: Custom React Hooks are first-class citizens for organizing our business logic and state. Each Hook encapsulates a distinct, cohesive functional module (e.g., WebRTC connection, room management), making the logic unit reusable, testable, and significantly simplifying the component tree.
- Layered Architecture: The codebase follows a clear, layered structure, ensuring that each layer has a single responsibility, reducing coupling between modules.
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- UI: React 18, Tailwind CSS, shadcn/ui (based on Radix UI)
- State Management: Zustand + custom React Hooks (modular business logic with shared app state)
- WebRTC Signaling: Socket.IO Client
- Data Fetching: React Server Components (RSC), Fetch API
- Internationalization:
next/servermiddleware + dynamic JSON dictionaries - Content: MDX (for blog and static content pages)
The frontend architecture can be broadly divided into four layers:
graph TD
A["<b>① Application & Routing Layer (app/)</b><br/><br/>Pages, Layouts, Routing, i18n Middleware, Data Fetching"]
B["<b>② UI & Component Layer (components/)</b><br/><br/>Coordinator Components, UI Panels, Common Components, Base UI Elements"]
C["<b>③ Business Logic & State Layer (hooks/)</b><br/><br/>WebRTC Connection, Room Management, File Transfer, Clipboard Operations"]
D["<b>④ Core Libraries & Utilities Layer (lib/)</b><br/><br/>Low-level WebRTC Wrappers, File Handling, Utility Functions, API Client"]
style A fill:#f9f9f9,stroke:#333,stroke-width:1px
style B fill:#f9f9f9,stroke:#333,stroke-width:1px
style C fill:#f9f9f9,stroke:#333,stroke-width:1px
style D fill:#f9f9f9,stroke:#333,stroke-width:1px
A --> B --> C --> D
- ① Application & Routing Layer: Managed by the Next.js App Router, responsible for page rendering, route control, internationalization, and initial data fetching.
- ② UI & Component Layer: Responsible for displaying the entire user interface. It consumes state and methods from the layer below (Hooks) and propagates user interaction events upward.
- ③ Business Logic & State Layer: This is the "brain" of the application. It encapsulates all core functional business logic and state through a series of custom Hooks.
- ④ Core Libraries & Utilities Layer: Provides the lowest-level, framework-agnostic, pure functions, such as low-level WebRTC wrappers and API requests.
This section details how the application's most critical P2P transfer feature is implemented through the collaboration of different architectural layers.
- User Action (
components): The user performs an action inSendTabPanelorRetrieveTabPanel(e.g., selecting a file, entering a room code). - Logic Processing (
hooks): Hooks likeuseFileTransferHandleranduseRoomManagercapture these actions, manage related state (e.g., the list of files to be sent), and invoke connection methods provided by theuseWebRTCConnectionHook. - Connection Establishment (
hooks->lib):useWebRTCConnectioncalls low-level functions inlib/webrtc_*.tsto negotiate with the peer via the Socket.IO signaling server and establish anRTCPeerConnection. - Data Transfer (
lib): Once the connection is established,lib/fileSender.tsandlib/fileReceiver.tsare responsible for chunking, serializing, and transferring the file over theRTCDataChannel. - State Updates & Callbacks (
lib->hooks->components): During the transfer, theliblayer notifies thehookslayer of state changes (e.g., progress updates) via callbacks. These state changes in thehookslayer ultimately trigger a re-render of thecomponentslayer's UI.
-
Low-Level WebRTC Wrappers (
lib/):webrtc_base.ts: Encapsulates interactions with the Socket.IO signaling server, generic management ofRTCPeerConnection(ICE, connection state), and the creation ofRTCDataChannel. It is the foundation for all WebRTC operations.fileSender.ts/fileReceiver.ts: Handle the logic for sending and receiving files/text, including metadata exchange, file chunking, progress calculation, data reassembly, and file saving.
-
WebRTC Business Logic Wrappers (
hooks/):useWebRTCConnection.ts: The connection's "central hub". It initializes and managessenderandreceiverinstances, handles the connection lifecycle, and provides a clean API (e.g.,broadcastDataToAllPeers) and state (e.g.,peerCount,sendProgress) to the upper layers.useRoomManager.ts: The room's "state machine". It's responsible for the creation, validation (with debouncing), and joining logic for room IDs, and manages UI state text related to the room.useFileTransferHandler.ts: The transfer's "data center". It manages the text and files to be sent or received, handles user actions like adding/removing/downloading files, and provides callbacks touseWebRTCConnectionfor processing received data.
-
UI Coordination & Display (
components/):ClipboardApp.tsx: The core application's "main coordinator". It contains no business logic itself. Its sole responsibility is to integrate all the core Hooks mentioned above and then distribute the state and callbacks obtained from these Hooks as props to specific UI sub-components. Furthermore, it is responsible for listening to global drag-and-drop events and rendering a full-screen drop zone when the user drags files anywhere into the window, significantly improving the file upload experience.SendTabPanel.tsx/RetrieveTabPanel.tsx: Purely presentational components responsible for rendering the send and receive panels and responding to user input.FileListDisplay.tsx: Used to display the file list and transfer status.FullScreenDropZone.tsx: A simple UI component that displays a full-screen, semi-transparent overlay when files are being dragged globally, providing clear visual feedback to the user.
-
frontend/app/: Core application routes and pages.[lang]/: Implements multi-language dynamic routing.layout.tsx: Global layout, provides Providers (Theme, i18n).page.tsx: Main page entry point, rendersHomeClient.HomeClient.tsx: (Client Component) Hosts the coreClipboardAppand other marketing/display components.
config/: Application configuration.api.ts: Centralizes interaction with the backend API.environment.ts: Manages environment variables and runtime configurations (like ICE servers).
-
frontend/components/: UI component library.ClipboardApp/: All UI sub-components broken down fromClipboardApp. Implements separation of concerns.common/: Reusable components that can be used across the project (e.g.,YouTubePlayer).ui/: (from shadcn/ui) Base atomic components.web/: Large, page-level static components for the website (e.g.,Header,Footer).Editor/: Custom rich text editor.
-
frontend/hooks/: The core of business logic and state management. Detailed above. -
frontend/lib/: Core libraries and utility functions.webrtc_*.ts,fileSender.ts,fileReceiver.ts: WebRTC core.dictionary.ts: Internationalization dictionary loading.utils.ts,fileUtils.ts: General utility functions.fileUtils.tscontains core logic for handling files and directories, such astraverseFileTree.
-
frontend/types/: Global TypeScript type definitions. -
frontend/constants/: Application-wide constants, mainly for i18n configuration and message files.
The project adopts a combined approach of Zustand + custom Hooks:
- Zustand (shared app state): Manages cross-page/component application-level state such as room/connection states, send/receive progress, and UI states. Implementation lives in
frontend/stores/fileTransferStore.ts, offering minimal boilerplate and strong type support. - Custom Hooks (cohesive business logic): Complex flows (WebRTC connection, room management, file transfer orchestration) remain encapsulated within Hooks, preserving cohesion, testability, and reusability.
Benefits:
- Clear boundaries: Observable/shared data goes to Zustand; highly cohesive/transient state stays in each module/Hook.
- Less boilerplate & maintainable: Zustand remains lightweight while Hooks keep composability and testability.
- Aligned with reality: Keeps documentation consistent with the actual implementation.
- Route-Driven: Language switching is implemented via the URL path (
/[lang]/). - Automatic Detection:
middleware.tsintercepts requests and automatically redirects to the appropriate language path based on theAccept-Languageheader or a cookie. - Dynamic Loading: The
getDictionaryfunction inlib/dictionary.tsasynchronously loads the correspondingmessages/*.jsonfile based on thelangparameter, enabling code splitting.
- Singleton Store (Zustand):
frontend/stores/fileTransferStore.tsis a module-level singleton, preserving in-memory state across routes (e.g., share content, files to send, received files/meta, progress states). - Singleton Connection Service (webrtcService):
frontend/lib/webrtcService.tsholdsRTCPeerConnection/RTCDataChanneland FileSender/FileReceiver as a singleton. App Router page switches do not tear it down. - Effect: In the same browser tab, in-app navigation (App Router page switches) does not interrupt ongoing transfers, and selected/received content remains intact.
- Boundary: Page refresh/closing the tab or opening in a new tab is not covered; when changing layout hierarchy/SSR behavior, avoid cleaning the connection in layout unmount.
- Note: Do not call
webrtcService.leaveRoom()or reset the global Store inside route-change side effects; only do so on explicit user actions.
The current frontend architecture successfully deconstructs a complex WebRTC application into a series of clean, maintainable modules through layered design and Hook-centric logic encapsulation. The boundaries between UI, business logic, and underlying libraries are clear, laying a solid foundation for future feature expansion and maintenance.
Future areas for optimization include:
- Adding Unit/Integration Tests: Writing test cases for core Hooks (
useWebRTCConnection, etc.) and utility classes inlib. - Bundle Analysis: Regularly analyzing the bundle size with
@next/bundle-analyzerto identify optimization opportunities. - Component Library Enhancement: Continuously refining and polishing the common components in
components/common.