Travel Stash

Next-gen travel app. Vercel ai sdk, claude haiku and RxDB allow you to generate complete travel itineraries. Store your data locally using RxDB and retrieve it offline when your international data plan runs out.

Local First
Offline Data Synchronization
Cross-Device State Management
Progressive Web App (PWA)
Next.js
Tailwind CSS
Claude AI
RXDB Progressive Web App
Live Preview
Travel Stash image

Key Features

Local First

Data is stored locally in your browser, ensuring that users can access and interact with the app even without an internet connection.

Offline Data Synchronization

Implement RxDB for robust offline-first data management, ensuring seamless sync between local and remote databases.

Cross-Device State Management

Utilize React Query for efficient state management and data fetching across multiple devices and network conditions.

Progressive Web App (PWA)

Implement service workers and a web app manifest for a native app-like experience, enabling offline functionality and home screen installation.

Documentation

Cult Offline Travel Stash

Cult Offline Travel Stash is an offline-first travel planning application designed to help users manage their itineraries and travel lists seamlessly.

Installation

  1. Unzip the repository:

    sh
    unzip cult-offline-travel-stash.zip cd cult-offline-travel-stash
  2. Install dependencies:

    sh
    pnpm install
  3. Create an .env file:

    Only one of the following environment variables is required.

    plaintext
    OPENAI_API_KEY=your-openai-api-key GROQ_API_KEY=your-groq-api-key ANTHROPIC_API_KEY=your-anthropic-api-key
  4. Run the development server:

    sh
    pnpm dev

    The application will run at http://localhost:3000.

Notable Dependencies

  • Next.js: A React framework for server-side rendering, static site generation, and more.
  • @ducanh2912/next-pwa: Adds PWA capabilities to the Next.js app, enabling offline functionality.
  • @ai-sdk/openai, @ai-sdk/anthropic: SDKs for integrating AI capabilities from OpenAI and Anthropic.
  • zod: A TypeScript-first schema declaration and validation library.
  • RxDB: A real-time NoSQL database for JavaScript applications, which supports offline-first features.
  • rxjs: A library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code.
  • rxdb-hooks: Provides React hooks for RxDB, enabling seamless integration with React components.

Project Structure

.
├── app
│   ├── [id]
│   │   ├── page.tsx
│   ├── api
│   │   ├── create-itinerary
│   │   │   ├── route.ts
│   │   ├── edit-itinerary
│   │   │   ├── route.ts
│   │   ├── ai-json-completion.ts
│   │   ├── prompts.ts
│   │   ├── schemas.ts
│   ├── share
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   ├── provider.tsx
├── components
│   ├── cult
│   ├── forms
│   ├── ui
│   │   ├── action-button-drawer.tsx
│   │   ├── cards.tsx
│   │   ├── dashboard.tsx
│   │   ├── grouped-list.tsx
│   │   ├── list-filter.tsx
│   │   ├── list-item-card.tsx
│   │   ├── list-tabs.tsx
│   │   ├── loading-button.tsx
│   │   ├── logo.tsx
│   │   ├── nav.tsx
│   │   ├── trip-overview-card.tsx
│   │   ├── trip-overview-list.tsx
├── db
│   ├── initialize.ts
│   ├── model.tsx
├── lib
├── node_modules
├── public
├── .env.example
├── .eslintrc.json
├── .gitignore
├── components.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── README.md
├── tailwind.config.ts
└── tsconfig.json

Your documentation looks great! Here's a slightly refined version to ensure clarity and flow:


Offline-First? What's that?

An offline-first application ensures that users can access and interact with the app even without an internet connection. This is achieved using technologies like service workers and offline storage mechanisms.

1. Offline DB - RxDB Model

RxDB (Reactive Database) is a NoSQL database for JavaScript applications. It provides real-time capabilities and supports offline functionality, making it a perfect choice for building offline-first applications. Here's a detailed look at how RxDB is used in this project:

Schema Definitions

Trips Schema

The tripsSchema defines the structure of the trips collection:

typescript
const tripsSchema = { version: 0, primaryKey: "tripId", type: "object", properties: { tripId: { type: "string", maxLength: 100 }, tripName: { type: "string", maxLength: 100 }, }, required: ["tripId", "tripName"], };
Travel Itinerary Schema

The travelItinerarySchema defines the structure of the travel itinerary collection:

typescript
const travelItinerarySchema = { version: 0, primaryKey: "id", type: "object", properties: { id: { type: "string", maxLength: 100 }, tripId: { type: "string", maxLength: 100, ref: "trips_v0" }, // Reference to the trips_v0 table tripName: { type: "string", maxLength: 100 }, title: { type: "string", maxLength: 100 }, description: { type: "string", maxLength: 500 }, image: { type: "string", format: "uri" }, location: { type: "object", properties: { name: { type: "string", maxLength: 100 }, address: { type: "string", maxLength: 200 }, latitude: { type: "number", minimum: -90, maximum: 90 }, longitude: { type: "number", minimum: -180, maximum: 180 }, placeId: { type: "string" }, }, required: ["name", "address"], }, day: { type: "string" }, timeOfDay: { type: "string", maxLength: 50 }, mapLink: { type: "string", format: "uri" }, status: { type: "string", enum: ["done", "not done", "later"] }, priority: { type: "string", enum: ["low", "medium", "high"] }, notes: { type: "string", maxLength: 1000 }, dueDate: { type: "string", format: "date" }, estimatedTime: { type: "string", maxLength: 50 }, reminderEnabled: { type: "boolean" }, reminderTime: { type: "string", format: "date-time" }, createdBy: { type: "string" }, lastUpdatedBy: { type: "string" }, category: { type: "string", maxLength: 50 }, startDateTime: { type: "string", format: "date-time" }, sequence: { type: "integer", minimum: 0 }, }, required: ["id", "title", "status", "startDateTime", "sequence"], };

Database Initialization

The initialize function sets up the database and adds the required collections:

typescript
export const initialize = async () => { // Add plugins required for RxDB await addRxPlugin(RxDBDevModePlugin); await addRxPlugin(RxDBQueryBuilderPlugin); await addRxPlugin(RxDBUpdatePlugin); // Create RxDB const db = await createRxDatabase({ name: "mydatabase", // Change this to whatever database name you want storage: getRxStorageDexie(), ignoreDuplicate: true, }); await db.addCollections({ trips_v0: { schema: tripsSchema, }, trip_itinerary_v0: { schema: travelItinerarySchema, }, }); return db; };

Core Dependencies for RxDB

  • rxdb: The core library for RxDB, providing real-time and offline-first capabilities.
  • rxdb-hooks: Provides React hooks for RxDB, enabling seamless integration with React components.
  • rxjs: A library for reactive programming using Observables, making it easier to compose asynchronous or callback-based code.

2. Offline Next.js - PWA

What Are Progressive Web Apps (PWAs)?

Progressive Web Apps (PWAs) are web applications that combine the best features of web and mobile apps. They are designed to be reliable, fast, and engaging, providing a native app-like experience on the web.

Key Features of PWAs

  1. Offline Capability: PWAs can function offline or on low-quality networks by caching essential resources.
  2. Installable: Users can install PWAs on their devices, adding them to the home screen for quick access, just like native apps.
  3. Responsive: PWAs work on any device with a responsive design, ensuring a seamless experience across different screen sizes.
  4. App-like Interactions: PWAs offer smooth interactions and navigation similar to native apps, enhancing user engagement.
  5. Re-engageable: They support push notifications to re-engage users with timely updates and content.

How Do PWAs Work?

  1. Service Workers: At the core of PWAs are service workers, which are JavaScript files that run separately from the main browser thread. They enable features like offline functionality, background sync, and push notifications by intercepting network requests and serving cached assets when needed.
  2. Web App Manifest: The manifest file is a JSON file that provides metadata about the app, such as the name, icons, theme colors, and display settings. This file allows the browser to recognize and install the PWA on the user's device.
  3. HTTPS: PWAs must be served over HTTPS to ensure security and privacy. This secure context is required for service workers and many other modern web APIs to function.

Example Workflow of a PWA

  1. Initial Load: When the user first visits the PWA, the service worker is registered, and essential assets (HTML, CSS, JavaScript, images) are cached.
  2. Subsequent Visits: On subsequent visits, the service worker intercepts network requests and serves the cached assets, making the app load faster and work offline if the network is unavailable.
  3. Updates: The service worker periodically checks for updates and caches new versions of the assets in the background, ensuring that the user always has the latest version without interrupting their experience.
  4. Offline Mode: If the user is offline, the service worker serves the cached assets and fallback content (such as an offline page) to keep the app functional.

Integrating PWAs with Next.js

In the context of Next.js, PWAs are integrated using plugins like @ducanh2912/next-pwa, which simplifies the process of setting up service workers and caching strategies. By configuring the plugin, you can enhance your Next.js app with PWA capabilities, providing a reliable, fast, and engaging user experience even in offline scenarios.

Next.js PWA

This project uses @ducanh2912/next-pwa to add PWA capabilities, ensuring the application can work offline and provide a native app-like experience. Here's how it is integrated:

  1. Install @ducanh2912/next-pwa:

    sh
    pnpm install @ducanh2912/next-pwa
  2. Configure next.config.js:

    javascript
    const withPWA = require("@ducanh2912/next-pwa").default({ cacheOnFrontEndNav: true, aggressiveFrontEndNavCaching: true, reloadOnOnline: true, swcMinify: true, dest: "public", fallbacks: { document: "/offline", // Custom offline fallback page }, workboxOptions: { disableDevLogs: true, }, }); /** @type {import('next').NextConfig} */ const nextConfig = { // Other Next.js configurations }; module.exports = withPWA(nextConfig);

Explanation of the PWA Configuration Options:

  • cacheOnFrontEndNav: true: This option caches navigations on the frontend, enhancing performance by reducing server requests for frequently visited pages.
  • aggressiveFrontEndNavCaching: true: Implements more aggressive caching strategies for frontend navigation, ensuring faster load times and improved offline capabilities.
  • reloadOnOnline: true: Forces a reload of the page when the network connection is restored, ensuring that users see the latest content when they come back online.
  • swcMinify: true: Enables SWC-based minification for faster build times and optimized performance.
  • dest: "public": Specifies the directory where the service worker and other PWA assets are generated.
  • fallbacks: Defines fallback assets to use when the network is unavailable

. In this case:

  • document: "/offline": Specifies a custom offline fallback page to be served when the user is offline.
  • workboxOptions: Provides additional configurations for Workbox, the tool used by next-pwa to generate service workers.
    • disableDevLogs: true: Disables Workbox logs in the development environment to keep the console clean.

By setting up PWA, the app can cache resources and work offline, enhancing the user experience, especially in areas with limited or no internet connectivity.


If you run into any issues or have any questions, feel free to reach out on Twitter @nolansym.


App Screenshots

Travel Stash
Travel Stash
Travel Stash
Travel Stash