Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
"lib/",
"coverage/",
"node_modules/",
"example/dist/"
"example/dist/",
"docs/.next/",
"docs/.source/",
"docs/out/",
"docs/content/"
]
}
5 changes: 4 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
"lib/",
"coverage/",
".yarn/",
"example/dist/"
"example/dist/",
"docs/.next/",
"docs/.source/",
"docs/out/"
],
"rules": {
"no-cond-assign": "warn",
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ The `package.json` file contains various scripts for common tasks:
- `yarn example start`: start the Metro server for the example app.
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.
- `yarn docs:dev`: start the docs site dev server (Next.js + Fumadocs).
- `yarn docs:build`: build the docs site for production.

### Sending a pull request

Expand Down
16 changes: 16 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Next.js
.next/
out/
next-env.d.ts

# Fumadocs MDX generated source
.source/

# logs
*.log

# typescript
*.tsbuildinfo

# misc
.DS_Store
45 changes: 45 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Docs site

Documentation site for `react-native-preview-url`, built with
[Fumadocs](https://fumadocs.dev) on Next.js 16.

## Local development

From the repo root:

```sh
yarn docs:dev # Next.js dev server on http://localhost:3000
yarn docs:build # production build
```

The library is consumed via a workspace symlink. A `postinstall` script
(`scripts/link-workspace-root.mjs`) creates the symlink because Yarn 3 doesn't
auto-link the root workspace into a sibling workspace's `node_modules`.

The site uses `react-native-web` to render the actual `LinkPreview` component
inside MDX (`<LinkPreviewDemo url="..." />`). The aliasing happens in
`next.config.mjs`.

## Deploy to Vercel

1. Connect this repo to a Vercel project.
2. **Root Directory**: `docs`
3. **Framework Preset**: Next.js (auto-detected)
4. **Build Command**: `yarn build` (auto-detected)
5. **Install Command**: `yarn install` (Vercel runs from the repo root since
it's a yarn workspace)

No env vars are required — the site is fully static apart from the
`/api/search` route.

## Content

MDX lives under `content/docs/`. The page tree is generated automatically
from filenames and `meta.json` — see
[Page Conventions](https://fumadocs.dev/docs/page-conventions).

To add a new page:

1. Create `content/docs/my-page.mdx` with `title:` + `description:` frontmatter.
2. Add the slug to `content/docs/meta.json` if you want to control its
position (otherwise it will sort alphabetically).
126 changes: 126 additions & 0 deletions docs/content/docs/api-reference.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: API reference
description: Every exported symbol, with signatures.
---

The package's public surface lives in `react-native-preview-url`:

```ts
import {
LinkPreview, // default and named
useUrlPreview,
setBaseUrl,
configureCache,
clearCache,
invalidateUrl,
type CacheOptions,
} from 'react-native-preview-url';
```

## Components

### `LinkPreview`

Rendered card. See [LinkPreview](/docs/link-preview).

```ts
const LinkPreview: React.FC<LinkPreviewProps>;
```

## Hooks

### `useUrlPreview`

Fetch + cache metadata. See [useUrlPreview](/docs/use-url-preview).

```ts
function useUrlPreview(
url: string,
timeout?: number
): {
loading: boolean;
data: LinkPreviewResponse | null;
error: string | null;
};
```

## Configuration

### `setBaseUrl`

Override the proxy API endpoint. Defaults to
`https://azizbecha-link-preview-api.vercel.app`. Throws `TypeError` for
non-`http(s)` strings. Trailing slashes are stripped.

```ts
import { setBaseUrl } from 'react-native-preview-url';

setBaseUrl('https://my-link-preview.example.com');
```

You can self-host the proxy from
[the source repo](https://github.com/azizbecha/react-native-preview-url)
to avoid the public endpoint entirely.

### `configureCache`

Reset and reconfigure the global metadata cache. See [Caching](/docs/caching).

```ts
function configureCache(options: CacheOptions): void;

interface CacheOptions {
maxSize?: number; // default 50
ttl?: number; // ms; default 300_000 (5 min)
errorTtl?: number; // ms; default 30_000 (30 s)
enabled?: boolean; // default true
}
```

### `clearCache`

Remove every entry. In-flight requests are unaffected.

```ts
function clearCache(): void;
```

### `invalidateUrl`

Drop the cached entry for a specific URL. Next render that requests it will
re-fetch.

```ts
function invalidateUrl(url: string): void;
```

## Types

### `CacheOptions`

Exported. See [`configureCache`](#configurecache).

### `LinkPreviewResponse`

The parsed proxy response. **Currently not exported from the package root** —
if you need the type, import the package's source via your bundler or
re-declare the shape locally:

```ts
interface PreviewImage {
url: string;
width?: number;
height?: number;
}

interface LinkPreviewResponse {
url: string;
title?: string;
description?: string;
images?: PreviewImage[];
favicons?: string[];
mediaType?: string;
contentType?: string;
siteName?: string;
}
```
77 changes: 77 additions & 0 deletions docs/content/docs/caching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: Caching
description: TTL, sizing, and invalidation for the in-memory metadata cache.
---

The library ships with a single in-memory LRU cache shared across every
`LinkPreview` and `useUrlPreview` call in the app. There is one cache instance
per JS process — configure it once at app startup.

## Defaults

| Option | Default | Notes |
| ---------- | ----------------- | -------------------------------------------------------------------------- |
| `maxSize` | `50` | Number of entries (success + error). LRU eviction. |
| `ttl` | `300_000` (5 min) | TTL for successful responses. |
| `errorTtl` | `30_000` (30 s) | TTL for cached errors. Short on purpose so transient failures don't stick. |
| `enabled` | `true` | Set `false` to disable caching globally. |

## Configure at startup

`configureCache` resets the cache and applies new options. Call it once,
typically in your root file:

```ts
// App.tsx (or your entry file)
import { configureCache } from 'react-native-preview-url';

configureCache({
maxSize: 200,
ttl: 60 * 60 * 1000, // 1 hour for successes
errorTtl: 5 * 60 * 1000, // 5 min for errors
});
```

The cache is keyed by the proxy base URL plus the requested URL, so changing
the base URL (via [`setBaseUrl`](/docs/api-reference#setbaseurl)) effectively
gives you a fresh cache.

## Disable caching

```ts
configureCache({ enabled: false });
```

With caching disabled, every render that fetches a URL hits the network. You
probably don't want this in production — the cache is the main reason the
component feels instant on re-render.

## Invalidate a specific URL

```ts
import { invalidateUrl } from 'react-native-preview-url';

invalidateUrl('https://example.com/post/123');
```

Useful after you publish or update content and want the next render to pick up
fresh metadata.

## Clear everything

```ts
import { clearCache } from 'react-native-preview-url';

clearCache();
```

Drops all entries (success and error). Doesn't affect in-flight requests.

## What's not in scope

- **Persistent caching.** Entries live in memory only — they don't survive
app restarts. If you need persistence, wrap your storage with a small layer
that hydrates the cache on launch and writes through `useUrlPreview`'s
`onSuccess`.
- **Per-component cache options.** The cache is global. Different components
cannot have different TTLs.
69 changes: 69 additions & 0 deletions docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Introduction
description: A small, opinionated React Native component for rendering rich URL link previews.
---

`react-native-preview-url` is a single-purpose component library: paste a URL,
get back a link card with title, description, image, and domain — automatically
fetched via a proxy API that parses the page's Open Graph and meta tags.

## Highlights

- **Zero native code.** Pure JS/TS. Works on iOS, Android, and the web (via
React Native Web).
- **Built-in caching.** In-memory LRU with separate TTLs for successful
responses and errors. Globally configurable.
- **In-flight de-duplication.** Two `LinkPreview`s for the same URL share a
single network request.
- **Tiny.** ~21 kB packed, no runtime dependencies beyond React Native peers.

<Cards>
<Card
title="Installation"
href="/docs/installation"
description="Add the package and peer deps."
/>
<Card
title="The LinkPreview component"
href="/docs/link-preview"
description="Props, behavior, and a live demo."
/>
<Card
title="useUrlPreview hook"
href="/docs/use-url-preview"
description="Fetch metadata without rendering."
/>
<Card
title="Caching"
href="/docs/caching"
description="TTL, sizing, and invalidation."
/>
</Cards>

## Quick start

```tsx
import { LinkPreview } from 'react-native-preview-url';

export function MyScreen() {
return (
<LinkPreview
url="https://github.com"
onError={(e) => console.warn(e)}
onSuccess={(data) => console.log(data)}
titleLines={1}
descriptionLines={4}
/>
);
}
```

<LinkPreviewDemo url="https://github.com" titleLines={1} descriptionLines={4} />

## How it works

The component does not parse Open Graph metadata on-device. It calls a hosted
proxy API (`https://azizbecha-link-preview-api.vercel.app` by default) which
fetches the URL server-side, parses the metadata, and returns JSON. This
sidesteps CORS issues and the bundle cost of an HTML/OG parser. You can point
it at your own deployment via [`setBaseUrl`](/docs/api-reference#setbaseurl).
Loading
Loading