> ## Documentation Index
> Fetch the complete documentation index at: https://docs.everybite.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> Optimize your EveryBite integration for performance and user experience

Recommendations for building great personalized menu experiences with EveryBite.

## Performance

### Prefetch Filter Options

Load filter options when the menu screen initializes, not when the user opens the filter panel:

```javascript theme={null}
// Good: Prefetch on screen load (uses X-Session-ID header for context)
useEffect(() => {
  prefetchFilterOptions();
}, [sessionId]);

// Bad: Fetch when filter panel opens
const onFilterPress = async () => {
  const filters = await fetchFilterOptions(); // Causes delay
  showFilterPanel(filters);
};
```

### Use Pagination

Never fetch all dishes at once. Use cursor-based pagination:

```graphql theme={null}
query SearchDishes($pagination: PaginationArgs) {
  search(pagination: $pagination) {
    matches {
      dish { id name }
      matchStatus
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

<Info>
  Chain context comes from your session via the `X-Session-ID` header. You don't need to pass `chainId` in the query.
</Info>

### Optimize Field Selection

Only request fields you need. GraphQL lets you be specific:

```graphql theme={null}
# Good: Request only needed fields
query {
  search(...) {
    matches {
      dish {
        id
        name
        imageUrl
        nutrition { calories }
      }
    }
  }
}

# Bad: Request everything
query {
  search(...) {
    matches {
      dish {
        id
        name
        description
        imageUrl
        nutrition { ...allFields }
        allergens { ...allFields }
        ingredients { ...allFields }
      }
    }
  }
}
```

## User Experience

### Display Match Status Clearly

Use visual hierarchy to communicate match status:

| Status         | Display              | Color         |
| -------------- | -------------------- | ------------- |
| `MATCH`        | Show prominently     | Green accent  |
| `ALMOST_MATCH` | Show with warning    | Yellow/Orange |
| `NOT_MATCH`    | De-emphasize or hide | Gray/Muted    |

### Show Match Reasons

When a dish is an "Almost Match", explain why:

```javascript theme={null}
{almostMatches.map(({ dish, matchReasons }) => (
  <DishCard
    dish={dish}
    warnings={matchReasons} // ["Contains Dairy", "Above calorie limit"]
  />
))}
```

### Persist Preferences

Save user preferences locally for returning users:

```javascript theme={null}
// Save preferences after user sets them
localStorage.setItem('guestPreferences', JSON.stringify(preferences));

// Restore on app launch
const savedPrefs = localStorage.getItem('guestPreferences');
if (savedPrefs) {
  applyPreferences(JSON.parse(savedPrefs));
}
```

## Security

### Protect API Keys

<Warning>
  Never expose API keys in client-side code. Always proxy requests through your backend.
</Warning>

```javascript theme={null}
// Bad: API key in client
fetch('https://api.everybite.com/smartmenu/graphql', {
  headers: { 'Authorization': 'pk_secret_key' } // Exposed!
});

// Good: Proxy through your backend
fetch('/api/menu/search', { body: preferences });
// Your backend adds the API key server-side
```

### Validate Session IDs

Generate session IDs server-side or use cryptographically secure methods:

```javascript theme={null}
// Good: Secure session ID
const sessionId = `sess_${crypto.randomUUID()}`;

// Bad: Predictable session ID
const sessionId = `sess_${userId}_${Date.now()}`; // Guessable
```

### Passing IDs Securely

All communication with the SmartMenu API happens over HTTPS, which means every request—including the `startSession` mutation and `X-Session-ID` header—is encrypted in transit. No one can intercept or read these values as they travel between your servers and ours.

IDs like `guestId` and `passportId` are passed once when you call `startSession`. After that, only the `X-Session-ID` header is needed on subsequent calls. Follow these best practices for the IDs you send:

**Use opaque identifiers**

Use random, meaningless strings rather than sequential numbers that could be guessed:

```javascript theme={null}
// Good: Random, unpredictable
guestId: 'usr_7Kx9mP2qL4nR8vT1'

// Avoid: Sequential, guessable
guestId: 'user_123'
```

**Never use personal information as IDs**

Don't use email addresses, phone numbers, or other personal data as identifiers:

```javascript theme={null}
// Good: Opaque reference
guestId: 'lyl_8Hj2kM5pQ9'

// Avoid: Exposes personal information
guestId: 'john.doe@email.com'
```

**Hash IDs if you prefer not to share them**

If you'd rather not send your internal customer IDs to EveryBite, you can hash them before sending. Use a consistent salt so you can still correlate analytics data later:

```javascript theme={null}
// Hash your internal ID before sending
const hashedId = sha256(internalCustomerId + YOUR_SECRET_SALT);
const guestId = hashedId;

// You can always recreate the same hash to correlate data
```

<Tip>
  As long as you're proxying API calls through your backend (as recommended above), these IDs only travel server-to-server and are never exposed to end users.
</Tip>

## Common Pitfalls

<AccordionGroup>
  <Accordion title="Not handling empty results">
    Always have UI for when no dishes match the user's preferences. Suggest loosening filters.
  </Accordion>

  <Accordion title="Ignoring Almost Matches">
    Almost Matches are valuable - they show dishes that are close. Don't hide them completely.
  </Accordion>

  <Accordion title="Caching personalized results">
    Search results are personalized per user. Don't cache them across users.
  </Accordion>

  <Accordion title="Missing error states">
    Handle network errors, rate limits, and API errors gracefully with retry logic.
  </Accordion>
</AccordionGroup>
