@nxisai/nxis 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy.yml +110 -0
- package/README.md +401 -0
- package/bin/deploy.sh +2 -0
- package/build/api.d.ts +244 -0
- package/build/auth.d.ts +91 -0
- package/build/core.d.ts +56 -0
- package/build/index.cjs.js +423 -0
- package/build/index.d.ts +33 -0
- package/build/index.esm.js +417 -0
- package/build/nxis.js +1 -0
- package/build/server.d.ts +77 -0
- package/docs/ssr.md +606 -0
- package/nxisbucket.json +16 -0
- package/package.json +53 -0
- package/rollup.config.js +50 -0
- package/src/api.ts +281 -0
- package/src/auth.ts +171 -0
- package/src/client.ts +104 -0
- package/src/core.ts +124 -0
- package/src/index.ts +33 -0
- package/src/server.ts +106 -0
- package/tsconfig.json +66 -0
package/docs/ssr.md
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
# Server-Side Rendering (SSR)
|
|
2
|
+
|
|
3
|
+
For SSR frameworks, Nxis.js provides a `NxisClient` class that fetches JSON-LD at render time so structured data is present in the initial HTML response — no client-side JavaScript required.
|
|
4
|
+
|
|
5
|
+
**Why SSR matters:** While search engines like Google can render JavaScript, large language models and LLM-based agents do not consistently execute JavaScript when crawling web pages. If structured data is only injected client-side (via the browser `<script>` tag), LLMs and AI agents may never see it. SSR ensures that JSON-LD is embedded directly in the initial HTML response, making your structured data immediately visible to every consumer — from traditional search engine crawlers to LLM training pipelines and AI agents browsing on behalf of users.
|
|
6
|
+
|
|
7
|
+
## Supported Frameworks
|
|
8
|
+
|
|
9
|
+
| Framework | Rendering | Method |
|
|
10
|
+
|-----------|-----------|--------|
|
|
11
|
+
| **Next.js** (App Router & Pages Router) | SSR / SSG | `NxisClient` |
|
|
12
|
+
| **Nuxt 3** | SSR / SSG | `NxisClient` |
|
|
13
|
+
| **Remix** | SSR | `NxisClient` |
|
|
14
|
+
| **Astro** | SSR / SSG | `NxisClient` |
|
|
15
|
+
| **SvelteKit** | SSR / SSG | `NxisClient` |
|
|
16
|
+
| **Express / Node.js** | Server | `NxisClient` |
|
|
17
|
+
| **Django / Rails / Laravel / Go / Any** | SSR / SSG | `curl` / HTTP API |
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
1. **Initialize**: Your application creates a `NxisClient` with OAuth client credentials. Tokens are managed automatically.
|
|
22
|
+
2. **Request**: At render time, the client calls the Nxis AI Proxy (`/v1/nxis:ssr`) with the page URI and a Bearer access token.
|
|
23
|
+
3. **Validation & Retrieval**: The Proxy validates the token, authorizes the requesting domain, and retrieves the structured data.
|
|
24
|
+
4. **Embedding**: The returned JSON-LD array is serialized directly into a `<script type="application/ld+json">` tag in the initial HTML response — visible to search engines, LLMs, and AI agents without requiring JavaScript execution.
|
|
25
|
+
|
|
26
|
+
### Sequence Diagram
|
|
27
|
+
|
|
28
|
+
```mermaid
|
|
29
|
+
sequenceDiagram
|
|
30
|
+
participant Crawler as Search Engine / LLM / Agent
|
|
31
|
+
participant App as SSR Application
|
|
32
|
+
participant Nxis as NxisClient
|
|
33
|
+
participant Auth as OAuth Token Endpoint
|
|
34
|
+
participant Proxy as Nxis AI Proxy
|
|
35
|
+
participant Service as Structured Data Service
|
|
36
|
+
|
|
37
|
+
Crawler->>App: Request Page
|
|
38
|
+
App->>Nxis: getStructuredData with uri, userAgent
|
|
39
|
+
alt Token cached & valid
|
|
40
|
+
Note over Nxis: Use cached token
|
|
41
|
+
else Token expired or missing
|
|
42
|
+
Nxis->>Auth: POST /v1/oauth/token (client_credentials)
|
|
43
|
+
Auth-->>Nxis: access_token + expires_in
|
|
44
|
+
end
|
|
45
|
+
Nxis->>Proxy: GET /v1/nxis:ssr with Bearer token and User-Agent
|
|
46
|
+
Proxy->>Service: Authorize & Fetch Data
|
|
47
|
+
alt Valid Access & Data Found
|
|
48
|
+
Service-->>Proxy: Returns JSON-LD
|
|
49
|
+
Proxy-->>Nxis: 200 OK (JSON-LD Data)
|
|
50
|
+
Nxis-->>App: Returns object array
|
|
51
|
+
App->>App: Embed JSON-LD in HTML
|
|
52
|
+
App-->>Crawler: Full HTML with structured data
|
|
53
|
+
else Invalid or No Data
|
|
54
|
+
Service-->>Proxy: Error / Empty
|
|
55
|
+
Proxy-->>Nxis: Error
|
|
56
|
+
Nxis-->>App: Throws Error
|
|
57
|
+
App-->>Crawler: HTML without structured data
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Getting Started
|
|
64
|
+
|
|
65
|
+
### Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @nxisai/nxis
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Environment Setup
|
|
72
|
+
|
|
73
|
+
Set these environment variables on your server:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
NXIS_API_HOST=https://api.nxis.ai
|
|
77
|
+
NXIS_CLIENT_ID=nxis_cid_... # Your OAuth client ID
|
|
78
|
+
NXIS_CLIENT_SECRET=nxis_sec_... # Your OAuth client secret
|
|
79
|
+
NXIS_TOKEN_URL=https://authorization.nxis.ai/v1/oauth/token # Optional, this is the default
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### API Reference
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
86
|
+
|
|
87
|
+
const nxis = new NxisClient({
|
|
88
|
+
clientId: string, // OAuth client ID
|
|
89
|
+
clientSecret: string, // OAuth client secret
|
|
90
|
+
tokenUrl?: string, // Optional token endpoint URL (defaults to NXIS_TOKEN_URL env var)
|
|
91
|
+
scope?: string, // Optional OAuth scope (defaults to "jsonld:read")
|
|
92
|
+
apiHost?: string, // Optional API host override (defaults to NXIS_API_HOST env var)
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const jsonLd: object[] = await nxis.getStructuredData({
|
|
96
|
+
uri: string, // The full URI of the page being rendered
|
|
97
|
+
siteId: string, // The site identifier
|
|
98
|
+
userAgent?: string, // Optional User-Agent for bot detection
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Returns a `Promise<object[]>` — the JSON-LD data array ready to be serialized into a `<script type="application/ld+json">` tag.
|
|
103
|
+
|
|
104
|
+
### Token Lifecycle
|
|
105
|
+
|
|
106
|
+
The SSR client uses **OAuth 2.0 client credentials** for authentication. Access tokens are managed automatically by `NxisClient`:
|
|
107
|
+
|
|
108
|
+
- **Lazy initialization**: No token is fetched until the first `getStructuredData` call.
|
|
109
|
+
- **In-memory cache**: Tokens are cached and reused for subsequent requests.
|
|
110
|
+
- **Auto-refresh**: Tokens are refreshed 60 seconds before expiration.
|
|
111
|
+
- **Concurrency-safe**: Multiple concurrent requests during a refresh all await the same token fetch.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Framework Examples
|
|
116
|
+
|
|
117
|
+
### Next.js (App Router)
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// app/page.tsx
|
|
121
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
122
|
+
import { headers } from 'next/headers';
|
|
123
|
+
|
|
124
|
+
const nxis = new NxisClient({
|
|
125
|
+
clientId: process.env.NXIS_CLIENT_ID!,
|
|
126
|
+
clientSecret: process.env.NXIS_CLIENT_SECRET!,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export default async function Page() {
|
|
130
|
+
const headersList = await headers();
|
|
131
|
+
const host = headersList.get('host') ?? 'localhost:3000';
|
|
132
|
+
const proto = headersList.get('x-forwarded-proto') ?? 'http';
|
|
133
|
+
const pageUri = `${proto}://${host}/`;
|
|
134
|
+
|
|
135
|
+
const jsonLd = await nxis.getStructuredData({
|
|
136
|
+
uri: pageUri,
|
|
137
|
+
siteId: process.env.NXIS_SITE_ID!,
|
|
138
|
+
userAgent: headersList.get('user-agent') ?? undefined,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
<script
|
|
144
|
+
type="application/ld+json"
|
|
145
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
146
|
+
/>
|
|
147
|
+
<main>{/* page content */}</main>
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Next.js (Pages Router)
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// pages/index.tsx
|
|
157
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
158
|
+
import Head from 'next/head';
|
|
159
|
+
import { GetServerSideProps } from 'next';
|
|
160
|
+
|
|
161
|
+
const nxis = new NxisClient({
|
|
162
|
+
clientId: process.env.NXIS_CLIENT_ID!,
|
|
163
|
+
clientSecret: process.env.NXIS_CLIENT_SECRET!,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
export const getServerSideProps: GetServerSideProps = async (context) => {
|
|
167
|
+
const jsonLd = await nxis.getStructuredData({
|
|
168
|
+
uri: `https://yoursite.com${context.resolvedUrl}`,
|
|
169
|
+
siteId: process.env.NXIS_SITE_ID!,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return { props: { jsonLd } };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export default function Page({ jsonLd }: { jsonLd: object[] }) {
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
<Head>
|
|
179
|
+
<script
|
|
180
|
+
type="application/ld+json"
|
|
181
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
182
|
+
/>
|
|
183
|
+
</Head>
|
|
184
|
+
<main>{/* page content */}</main>
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Nuxt 3
|
|
191
|
+
|
|
192
|
+
```html
|
|
193
|
+
<!-- pages/index.vue -->
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
196
|
+
|
|
197
|
+
const config = useRuntimeConfig();
|
|
198
|
+
const nxis = new NxisClient({
|
|
199
|
+
clientId: config.nxisClientId,
|
|
200
|
+
clientSecret: config.nxisClientSecret,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const { data: jsonLd } = await useAsyncData('nxis-jsonld', () =>
|
|
204
|
+
nxis.getStructuredData({
|
|
205
|
+
uri: `https://yoursite.com${useRoute().fullPath}`,
|
|
206
|
+
siteId: config.nxisSiteId,
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
useHead({
|
|
211
|
+
script: [
|
|
212
|
+
{
|
|
213
|
+
type: 'application/ld+json',
|
|
214
|
+
innerHTML: JSON.stringify(jsonLd.value),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
</script>
|
|
219
|
+
|
|
220
|
+
<template>
|
|
221
|
+
<main><!-- page content --></main>
|
|
222
|
+
</template>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Remix
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// app/routes/_index.tsx
|
|
229
|
+
import { json, type LoaderFunctionArgs } from '@remix-run/node';
|
|
230
|
+
import { useLoaderData } from '@remix-run/react';
|
|
231
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
232
|
+
|
|
233
|
+
const nxis = new NxisClient({
|
|
234
|
+
clientId: process.env.NXIS_CLIENT_ID!,
|
|
235
|
+
clientSecret: process.env.NXIS_CLIENT_SECRET!,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
239
|
+
const jsonLd = await nxis.getStructuredData({
|
|
240
|
+
uri: request.url,
|
|
241
|
+
siteId: process.env.NXIS_SITE_ID!,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return json({ jsonLd });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default function Index() {
|
|
248
|
+
const { jsonLd } = useLoaderData<typeof loader>();
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<>
|
|
252
|
+
<script
|
|
253
|
+
type="application/ld+json"
|
|
254
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
255
|
+
/>
|
|
256
|
+
<main>{/* page content */}</main>
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Astro
|
|
263
|
+
|
|
264
|
+
```astro
|
|
265
|
+
---
|
|
266
|
+
// src/pages/index.astro
|
|
267
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
268
|
+
|
|
269
|
+
const nxis = new NxisClient({
|
|
270
|
+
clientId: import.meta.env.NXIS_CLIENT_ID,
|
|
271
|
+
clientSecret: import.meta.env.NXIS_CLIENT_SECRET,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const jsonLd = await nxis.getStructuredData({
|
|
275
|
+
uri: Astro.url.href,
|
|
276
|
+
siteId: import.meta.env.NXIS_SITE_ID,
|
|
277
|
+
});
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
<html>
|
|
281
|
+
<head>
|
|
282
|
+
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
|
|
283
|
+
</head>
|
|
284
|
+
<body>
|
|
285
|
+
<!-- page content -->
|
|
286
|
+
</body>
|
|
287
|
+
</html>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### SvelteKit
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// src/routes/+page.server.ts
|
|
294
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
295
|
+
import { env } from '$env/static/private';
|
|
296
|
+
import type { PageServerLoad } from './$types';
|
|
297
|
+
|
|
298
|
+
const nxis = new NxisClient({
|
|
299
|
+
clientId: env.NXIS_CLIENT_ID,
|
|
300
|
+
clientSecret: env.NXIS_CLIENT_SECRET,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
export const load: PageServerLoad = async ({ url }) => {
|
|
304
|
+
const jsonLd = await nxis.getStructuredData({
|
|
305
|
+
uri: url.href,
|
|
306
|
+
siteId: env.NXIS_SITE_ID,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return { jsonLd };
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```html
|
|
314
|
+
<!-- src/routes/+page.svelte -->
|
|
315
|
+
<script lang="ts">
|
|
316
|
+
export let data;
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<svelte:head>
|
|
320
|
+
{@html `<script type="application/ld+json">${JSON.stringify(data.jsonLd)}</script>`}
|
|
321
|
+
</svelte:head>
|
|
322
|
+
|
|
323
|
+
<main><!-- page content --></main>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Express / Node.js
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const express = require('express');
|
|
330
|
+
const { NxisClient } = require('@nxisai/nxis');
|
|
331
|
+
|
|
332
|
+
const nxis = new NxisClient({
|
|
333
|
+
clientId: process.env.NXIS_CLIENT_ID,
|
|
334
|
+
clientSecret: process.env.NXIS_CLIENT_SECRET,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const app = express();
|
|
338
|
+
|
|
339
|
+
app.get('*', async (req, res) => {
|
|
340
|
+
const jsonLd = await nxis.getStructuredData({
|
|
341
|
+
uri: `https://yoursite.com${req.originalUrl}`,
|
|
342
|
+
siteId: process.env.NXIS_SITE_ID,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
res.send(`
|
|
346
|
+
<!DOCTYPE html>
|
|
347
|
+
<html>
|
|
348
|
+
<head>
|
|
349
|
+
<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>
|
|
350
|
+
</head>
|
|
351
|
+
<body><!-- page content --></body>
|
|
352
|
+
</html>
|
|
353
|
+
`);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
app.listen(3000);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## cURL / HTTP API (Django, Rails, Laravel, Go, etc.)
|
|
362
|
+
|
|
363
|
+
If you're using a non-JavaScript/TypeScript framework, you can call the Nxis SSR API directly over HTTP. The flow mirrors what `NxisClient` does internally:
|
|
364
|
+
|
|
365
|
+
1. **Obtain an access token** via the OAuth 2.0 client credentials grant.
|
|
366
|
+
2. **Fetch structured data** from the `/v1/nxis:ssr` endpoint using the Bearer token.
|
|
367
|
+
|
|
368
|
+
### Step 1 — Get an access token
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
curl -s -X POST https://authorization.nxis.ai/v1/oauth/token \
|
|
372
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
373
|
+
-d "grant_type=client_credentials" \
|
|
374
|
+
-d "client_id=${NXIS_CLIENT_ID}" \
|
|
375
|
+
-d "client_secret=${NXIS_CLIENT_SECRET}" \
|
|
376
|
+
-d "scope=jsonld:read"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Response:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"access_token": "eyJhbGciOi...",
|
|
384
|
+
"token_type": "Bearer",
|
|
385
|
+
"expires_in": 3600,
|
|
386
|
+
"scope": "jsonld:read"
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
> **Token caching:** Access tokens are valid for `expires_in` seconds. Cache the token and reuse it across requests — refresh it when it nears expiration.
|
|
391
|
+
|
|
392
|
+
### Step 2 — Fetch structured data
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
curl -s "https://api.nxis.ai/v1/nxis:ssr?uri=https://yoursite.com/page&site_id=${NXIS_SITE_ID}" \
|
|
396
|
+
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
397
|
+
-H "User-Agent: Googlebot/2.1"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Response:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"data": [
|
|
405
|
+
{
|
|
406
|
+
"@context": "https://schema.org",
|
|
407
|
+
"@type": "WebPage",
|
|
408
|
+
"name": "Example Page",
|
|
409
|
+
"url": "https://yoursite.com/page"
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Combined one-liner
|
|
416
|
+
|
|
417
|
+
Fetch a token and retrieve structured data in a single pipeline:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
ACCESS_TOKEN=$(curl -s -X POST https://authorization.nxis.ai/v1/oauth/token \
|
|
421
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
422
|
+
-d "grant_type=client_credentials" \
|
|
423
|
+
-d "client_id=${NXIS_CLIENT_ID}" \
|
|
424
|
+
-d "client_secret=${NXIS_CLIENT_SECRET}" \
|
|
425
|
+
-d "scope=jsonld:read" | jq -r '.access_token') && \
|
|
426
|
+
curl -s "https://api.nxis.ai/v1/nxis:ssr?uri=https://yoursite.com/page&site_id=${NXIS_SITE_ID}" \
|
|
427
|
+
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.data'
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Language Examples
|
|
431
|
+
|
|
432
|
+
<details>
|
|
433
|
+
<summary><strong>Python (Django, Flask, FastAPI)</strong></summary>
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
import requests
|
|
437
|
+
|
|
438
|
+
# Step 1: Get access token (cache this — valid for `expires_in` seconds)
|
|
439
|
+
token_resp = requests.post(
|
|
440
|
+
"https://authorization.nxis.ai/v1/oauth/token",
|
|
441
|
+
data={
|
|
442
|
+
"grant_type": "client_credentials",
|
|
443
|
+
"client_id": NXIS_CLIENT_ID,
|
|
444
|
+
"client_secret": NXIS_CLIENT_SECRET,
|
|
445
|
+
"scope": "jsonld:read",
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
access_token = token_resp.json()["access_token"]
|
|
449
|
+
|
|
450
|
+
# Step 2: Fetch structured data
|
|
451
|
+
ssr_resp = requests.get(
|
|
452
|
+
"https://api.nxis.ai/v1/nxis:ssr",
|
|
453
|
+
params={"uri": page_url, "site_id": NXIS_SITE_ID},
|
|
454
|
+
headers={
|
|
455
|
+
"Authorization": f"Bearer {access_token}",
|
|
456
|
+
"User-Agent": request.META.get("HTTP_USER_AGENT", ""),
|
|
457
|
+
},
|
|
458
|
+
)
|
|
459
|
+
json_ld = ssr_resp.json()["data"]
|
|
460
|
+
|
|
461
|
+
# Inject into your template as:
|
|
462
|
+
# <script type="application/ld+json">{{ json_ld|tojson }}</script>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
</details>
|
|
466
|
+
|
|
467
|
+
<details>
|
|
468
|
+
<summary><strong>Ruby (Rails, Sinatra)</strong></summary>
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
require 'net/http'
|
|
472
|
+
require 'json'
|
|
473
|
+
|
|
474
|
+
# Step 1: Get access token
|
|
475
|
+
token_uri = URI("https://authorization.nxis.ai/v1/oauth/token")
|
|
476
|
+
token_resp = Net::HTTP.post_form(token_uri, {
|
|
477
|
+
grant_type: "client_credentials",
|
|
478
|
+
client_id: ENV["NXIS_CLIENT_ID"],
|
|
479
|
+
client_secret: ENV["NXIS_CLIENT_SECRET"],
|
|
480
|
+
scope: "jsonld:read"
|
|
481
|
+
})
|
|
482
|
+
access_token = JSON.parse(token_resp.body)["access_token"]
|
|
483
|
+
|
|
484
|
+
# Step 2: Fetch structured data
|
|
485
|
+
ssr_uri = URI("https://api.nxis.ai/v1/nxis:ssr")
|
|
486
|
+
ssr_uri.query = URI.encode_www_form(uri: page_url, site_id: ENV["NXIS_SITE_ID"])
|
|
487
|
+
ssr_req = Net::HTTP::Get.new(ssr_uri)
|
|
488
|
+
ssr_req["Authorization"] = "Bearer #{access_token}"
|
|
489
|
+
ssr_req["User-Agent"] = request.user_agent
|
|
490
|
+
|
|
491
|
+
ssr_resp = Net::HTTP.start(ssr_uri.hostname, ssr_uri.port, use_ssl: true) { |http| http.request(ssr_req) }
|
|
492
|
+
json_ld = JSON.parse(ssr_resp.body)["data"]
|
|
493
|
+
|
|
494
|
+
# In your ERB template:
|
|
495
|
+
# <script type="application/ld+json"><%= json_ld.to_json %></script>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
</details>
|
|
499
|
+
|
|
500
|
+
<details>
|
|
501
|
+
<summary><strong>Go</strong></summary>
|
|
502
|
+
|
|
503
|
+
```go
|
|
504
|
+
package main
|
|
505
|
+
|
|
506
|
+
import (
|
|
507
|
+
"encoding/json"
|
|
508
|
+
"fmt"
|
|
509
|
+
"net/http"
|
|
510
|
+
"net/url"
|
|
511
|
+
"strings"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
func getAccessToken(clientID, clientSecret string) (string, error) {
|
|
515
|
+
data := url.Values{
|
|
516
|
+
"grant_type": {"client_credentials"},
|
|
517
|
+
"client_id": {clientID},
|
|
518
|
+
"client_secret": {clientSecret},
|
|
519
|
+
"scope": {"jsonld:read"},
|
|
520
|
+
}
|
|
521
|
+
resp, err := http.Post(
|
|
522
|
+
"https://authorization.nxis.ai/v1/oauth/token",
|
|
523
|
+
"application/x-www-form-urlencoded",
|
|
524
|
+
strings.NewReader(data.Encode()),
|
|
525
|
+
)
|
|
526
|
+
if err != nil {
|
|
527
|
+
return "", err
|
|
528
|
+
}
|
|
529
|
+
defer resp.Body.Close()
|
|
530
|
+
|
|
531
|
+
var result struct {
|
|
532
|
+
AccessToken string `json:"access_token"`
|
|
533
|
+
}
|
|
534
|
+
json.NewDecoder(resp.Body).Decode(&result)
|
|
535
|
+
return result.AccessToken, nil
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
func getStructuredData(token, pageURL, siteID, userAgent string) ([]interface{}, error) {
|
|
539
|
+
endpoint := fmt.Sprintf(
|
|
540
|
+
"https://api.nxis.ai/v1/nxis:ssr?uri=%s&site_id=%s",
|
|
541
|
+
url.QueryEscape(pageURL), url.QueryEscape(siteID),
|
|
542
|
+
)
|
|
543
|
+
req, _ := http.NewRequest("GET", endpoint, nil)
|
|
544
|
+
req.Header.Set("Authorization", "Bearer "+token)
|
|
545
|
+
if userAgent != "" {
|
|
546
|
+
req.Header.Set("User-Agent", userAgent)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
resp, err := http.DefaultClient.Do(req)
|
|
550
|
+
if err != nil {
|
|
551
|
+
return nil, err
|
|
552
|
+
}
|
|
553
|
+
defer resp.Body.Close()
|
|
554
|
+
|
|
555
|
+
var result struct {
|
|
556
|
+
Data []interface{} `json:"data"`
|
|
557
|
+
}
|
|
558
|
+
json.NewDecoder(resp.Body).Decode(&result)
|
|
559
|
+
return result.Data, nil
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
</details>
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## Adding Nxis SSR with Claude Code
|
|
568
|
+
|
|
569
|
+
You can use [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to automatically add Nxis SSR to an existing Next.js project. Paste the following prompt into Claude Code from your project's root directory:
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
Install the @nxisai/nxis package and add server-side structured data injection to my
|
|
573
|
+
Next.js App Router project. Here's what I need:
|
|
574
|
+
|
|
575
|
+
1. Install the @nxisai/nxis npm package
|
|
576
|
+
2. Add NXIS_API_HOST, NXIS_CLIENT_ID, and NXIS_CLIENT_SECRET to my .env.local
|
|
577
|
+
3. Create a shared NxisClient instance using `NxisClient` from `@nxisai/nxis/ssr`
|
|
578
|
+
initialized with the OAuth client credentials from environment variables
|
|
579
|
+
4. Update my root layout (or individual pages) to:
|
|
580
|
+
- Import `headers` from `next/headers`
|
|
581
|
+
- Derive the page URI from the incoming request's `host` and
|
|
582
|
+
`x-forwarded-proto` headers
|
|
583
|
+
- Pass the `user-agent` header for bot detection
|
|
584
|
+
- Inject the returned JSON-LD as a <script type="application/ld+json"> tag
|
|
585
|
+
|
|
586
|
+
Here's the API reference:
|
|
587
|
+
|
|
588
|
+
import { NxisClient } from '@nxisai/nxis/ssr';
|
|
589
|
+
|
|
590
|
+
const nxis = new NxisClient({
|
|
591
|
+
clientId: process.env.NXIS_CLIENT_ID!,
|
|
592
|
+
clientSecret: process.env.NXIS_CLIENT_SECRET!,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const jsonLd: object[] = await nxis.getStructuredData({
|
|
596
|
+
uri: pageUri,
|
|
597
|
+
siteId: process.env.NXIS_SITE_ID!,
|
|
598
|
+
userAgent: headersList.get('user-agent') ?? undefined,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
Tokens are managed automatically — no manual token handling needed.
|
|
602
|
+
Make sure JSON-LD is present in the initial HTML response so it's visible
|
|
603
|
+
to search engines, LLMs, and AI agents that don't render JavaScript.
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
This will guide Claude Code to install the package, configure environment variables, and wire up SSR structured data injection across your pages.
|
package/nxisbucket.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nxisai/nxis",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Javascript client for Nxis AI",
|
|
5
|
+
"main": "build/index.cjs.js",
|
|
6
|
+
"module": "build/index.esm.js",
|
|
7
|
+
"browser": "build/nxis.js",
|
|
8
|
+
"types": "build/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"browser": "./build/nxis.js",
|
|
12
|
+
"import": "./build/index.esm.js",
|
|
13
|
+
"require": "./build/index.cjs.js",
|
|
14
|
+
"types": "./build/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./ssr": {
|
|
17
|
+
"import": "./build/index.esm.js",
|
|
18
|
+
"require": "./build/index.cjs.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build:types": "tsc --declaration --emitDeclarationOnly --outDir build --target es2018 --skipLibCheck --moduleResolution node --esModuleInterop src/index.ts src/server.ts src/core.ts src/api.ts src/auth.ts",
|
|
24
|
+
"build": "source .env && npx rollup --config rollup.config.js --environment NXIS_API_HOST:$NXIS_API_HOST && npm run build:types",
|
|
25
|
+
"build:dev": "source .env && npx rollup --config rollup.config.js --environment NXIS_API_HOST:$NXIS_API_HOST && npm run build:types",
|
|
26
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git@github.com:nxisai/nxis-js.git"
|
|
31
|
+
},
|
|
32
|
+
"author": "Reggie McCray",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@rollup/plugin-node-resolve": "^13.0.6",
|
|
36
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
37
|
+
"@rollup/plugin-typescript": "^8.2.5",
|
|
38
|
+
"@types/google.analytics": "0.0.42",
|
|
39
|
+
"@types/uuid": "^8.3.4",
|
|
40
|
+
"rollup": "^2.58.0",
|
|
41
|
+
"rollup-plugin-inject-process-env": "^1.3.1",
|
|
42
|
+
"rollup-plugin-uglify": "^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"tslib": "^2.3.1",
|
|
46
|
+
"typescript": "^4.4.3",
|
|
47
|
+
"uuid": "^14.0.0",
|
|
48
|
+
"web-vitals": "^2.1.2"
|
|
49
|
+
},
|
|
50
|
+
"overrides": {
|
|
51
|
+
"serialize-javascript": "^7.0.5"
|
|
52
|
+
}
|
|
53
|
+
}
|