PixelSync is a real-time collaborative gallery application where users can view high-quality images, react with emojis, and comment instantly across all connected sessions.
- Node.js 18+ installed
- npm or yarn
-
Clone the repository (if you haven't already):
git clone <https://github.com/Sravan1011/Gallery-App.git> cd galleryapp
-
Install dependencies:
npm install # or yarn install -
Environment Variables: Create a
.env.localfile in the root directory and add your keys:NEXT_PUBLIC_UNSPLASH_ACCESS_KEY=your_unsplash_access_key NEXT_PUBLIC_INSTANTDB_APP_ID=your_instantdb_app_id
Note: If you don't provide an Unsplash key, the app will gracefully fall back to mock data.
-
Run the application:
npm run dev
Open http://localhost:3000 with your browser.
PixelSync uses a hybrid approach for data fetching:
-
Unsplash API (Images): We integrate directly with the Unsplash API on the client-side using the
unsplash-jsSDK.- Why Client-Side? For this real-time interaction demo, responsiveness was prioritized. The
createUnsplashinstance (exported asunsplash) is initialized with the public key. - Robustness: The app includes a robust fallback mechanism. If the API key is missing or rate limits are hit, a simulated mock data layer activates automatically, ensuring the UI never breaks.
- Why Client-Side? For this real-time interaction demo, responsiveness was prioritized. The
-
InstantDB (Interactions): All user interactions (reactions, comments) are handled via InstantDB, a modern database that syncs state to clients in real-time without needing a dedicated backend API server.
We utilize InstantDB for its "backend-less" real-time capabilities. The schema is inferred from usage but follows this structure:
Stores emoji reactions on specific images.
id: Unique ID (UUID)imageId: ID of the Unsplash photoemoji: The emoji character (e.g., "❤️", "🔥")userId: Session-based user IDtimestamp: Time of reaction
Stores text-based conversation.
id: Unique ID (UUID)imageId: ID of the Unsplash phototext: The comment contentuserId: Session-based user IDuserName: Generated alias (e.g., "User A1B2")userAvatar: DiceBear avatar URL based on userIDtimestamp: Time of comment
Usage Pattern:
We use the db.useQuery() hook in React components to subscribe to data. Changes made by one user (via db.transact()) are instantly pushed to all other connected clients viewing the same image.
-
Client-Side Rendering (CSR):
- The core gallery experience (
app/gallery/page.tsx) is a Client Component ('use client'). This is essential for the heavy interactivity required byframer-motionanimations and the real-time subscriptions of InstantDB.
- The core gallery experience (
-
Framer Motion:
- Used extensively for micro-interactions (hover cards, modal expansion) and layout transitions (staggered grid entry). This creates a "premium" feel compared to static CSS.
-
URL-Driven State:
- We use
next/navigation'suseSearchParamsto manage the selected photo state (?photoId=...). This allows deep-linking—users can share a URL to a specific photo, and the app correctly opens the modal on load.
- We use
-
Suspense Boundaries:
- Since we use
useSearchParamsin client components, we wrap the main page content in<Suspense>. This prevents Next.js de-optimization warnings and build errors related to accessing search parameters during static generation.
- Since we use
- Challenge: "useSearchParams() should be wrapped in a suspense boundary." The Next.js build failed because our client component accessed search parameters without a proper boundary.
- Solution: We extracted the main logic into a
GalleryContentcomponent and wrapped it with<Suspense fallback={<Loader />}>in the default export.
- Challenge: The Turbopack/Webpack parser in the build pipeline threw syntax errors on specific template literals containing complex expressions inside JSX attributes (e.g.,
router.push(\/gallery?...`)`). - Solution: We simplified these specific lines to standard string concatenation (
'/gallery?photoId=' + id), which is more robust across different parser versions.
- Challenge: Updating the URL when closing a modal caused re-renders that would sometimes re-open the modal or flicker.
- Solution: We implemented careful checks (
if (!photoId)) and utilizedscroll: falseinrouter.pushto maintain user scroll position while updating the history stack.
- Server-Side Rendering (SSR): Move the initial Unsplash data fetching to a Server Component. This would improve LCP (Largest Contentful Paint) and SEO by rendering the initial grid HTML on the server.
- Infinite Scrolling: Replace the "Next/Previous" pagination buttons with an
IntersectionObserver-based infinite scroll for a more fluid browsing experience. - Optimistic UI: Implement optimistic updates for comments/reactions so the UI updates literally instantly (0ms) while the database transaction confirms in the background.
- Virtualization: Use
react-windowfor the comments section to handle threads with thousands of comments efficiently.