This project, built using Next.js (Pages Router), demonstrates a custom web search interface that allows users to:
- Perform web searches using the Google Custom Search API.
- View search results, filtered for potential embeddability within an iframe.
- Browse selected search results directly within an embedded iframe component on the page.
- Track user interactions, including search queries and time spent viewing embedded pages, storing this data in MongoDB.
- Framework: Next.js (Pages Router)
- Runtime: Node.js
- UI Library: React
- Styling: Tailwind CSS
- State Management: React Hooks (useState, useRef, useEffect, useCallback)
- Database: MongoDB (via Mongoose or native driver, using
lib/mongodb.jsfor connection management) - APIs:
- Google Custom Search API (for fetching search results)
- Internal Next.js API Routes (
pages/api/) for search proxying and data tracking.
- Deployment: Designed for Vercel.
npm install
# Create a .env.local file (see Environment Variables section)
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen a URL like http://localhost:3000/[someUserID] (e.g., http://localhost:3000/test-user) in your browser to use the application. Replace [someUserID] with any identifier for the user session.
To learn more about the technologies used in this project, take a look at the following resources:
- Next.js Documentation – Learn about Next.js features and API.
- React Documentation – Learn about React.
- MongoDB Documentation – Learn how the database integration is managed.
- Tailwind CSS – For styling and responsive design guides.
- Google Custom Search API - For understanding the search backend.
You can check out the Next.js GitHub repository for more details.
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out the Next.js deployment documentation for more details.
To run this project locally, create a .env.local file in the root of the project and set the following variables:
MONGO_URI: The connection string for your MongoDB instance (e.g., MongoDB Atlas). (Required)MONGO_DB_NAME: The MongoDB database name (defaults toCustomSearchif not set inlib/mongodb.js).GOOGLE_CUSTOM_SEARCH_API_KEY: Your Google Custom Search API key. (Required)GOOGLE_CUSTOM_SEARCH_CX_ID: Your Google Custom Search Engine ID (CX ID). (Required)
This project uses the Next.js Pages Router. Here's a breakdown of key files and the application flow:
-
pages/[userID]/index.js:- This is the main user-facing page, accessed via a URL like
/user123. - It uses Next.js dynamic routing to capture the
userIDfrom the URL. - Manages the overall UI state: search query, loading status, search results, and whether the embedded browser is visible.
- Renders the main layout, including the persistent header
SearchBarand theSearchResultscomponent. - Handles user interactions: initiating searches via
handleSearchand displaying results in the embedded browser viahandleResultClick. - Contains the
EmbeddedBrowsercomponent, which is conditionally rendered when a result is clicked. - Implements tracking logic using
useEffectanduseCallbackhooks to record search events and click durations. - Uses
navigator.sendBeacon(with a fetch fallback) inuseEffectcleanup andvisibilitychangeevents to reliably send tracking data before the page unloads.
- This is the main user-facing page, accessed via a URL like
-
pages/api/search.js:- An API route that acts as a backend proxy for the Google Custom Search API.
- Receives search queries from the frontend (
pages/[userID]/index.js). - Fetches results from the Google API using the configured API key and CX ID.
- Implements pagination logic to potentially fetch multiple pages of results from Google.
- Performs embeddability checks:
isEmbeddable(): A quick domain blacklist check.isDisplayable(): A more reliable check using HEAD requests to inspectX-Frame-OptionsandContent-Security-Policyheaders.
- Filters and combines results, prioritizing likely embeddable ones, before sending them back to the frontend.
-
pages/api/track-data/[userID].js:- A dynamic API route for receiving and storing user interaction data.
- Captures the
userIDfrom the URL. - Receives POST requests containing search and click data (including URLs, timestamps, and durations) sent from
pages/[userID]/index.js. - Connects to MongoDB using the
connectToDatabasehelper. - Sanitizes incoming data (e.g., converting timestamps to Date objects, calculating durations).
- Uses MongoDB's
updateOnewithupsert: trueand$addToSetto efficiently add new search/click events to the user's session document in theUserBrowsingDatacollection.
-
lib/mongodb.js:- Utility module for managing the MongoDB connection.
- Implements connection caching to reuse connections across multiple API requests and during development hot-reloads, improving performance.
-
components/Directory:SearchBar.js: A reusable component for the search input and button. Used in both the main header and theEmbeddedBrowserheader.SearchResults.js: Renders the list of search results, handling clicks via theonResultClickprop.EmbeddedBrowser.js: The component that displays the selected website within an<iframe>. Includes its own header with a "Back" button (callingonClose) and anotherSearchBar.
-
styles/globals.css&tailwind.config.js:- Configure and apply global styling using Tailwind CSS.
-
.next/Directory:- Contains build artifacts generated by Next.js, including compiled code, manifests, and caches.
Workflow Summary:
- User navigates to
/[userID]. pages/[userID]/index.jsrenders, capturing theuserID.- User enters a query into the main
SearchBarand submits. handleSearchis called, sending the query topages/api/search.js./api/search.jsfetches results from Google, performs embeddability checks, and returns filtered results.pages/[userID]/index.jsreceives results and renders them usingSearchResults.- User clicks a result link.
handleResultClickis called, recording the click start time, setting theiframeUrl, and showing theEmbeddedBrowser.EmbeddedBrowserrenders, displaying the selected URL in the iframe.- User interacts with the embedded page or uses the
EmbeddedBrowser's "Back" button or search bar. - If "Back" is clicked,
closeEmbeddedBrowsercallsfinalizeClickto calculate duration and send click data to/api/track-data/[userID]. - If a new search is initiated (from either search bar),
handleSearchruns, closing the browser and starting the search flow again. - When the user leaves the page (closes tab/browser, navigates away),
beforeunloadandvisibilitychangelisteners trigger sending any remaining batched tracking data viasendBeaconto/api/track-data/[userID]. /api/track-data/[userID]receives the data and saves it to MongoDB.