@volvo-cars/content-delivery-client
This package is a wrapper around the Content Delivery API. It currently supports consuming dictionaries.
Quick Start
First add @volvo-cars/content-delivery-client
to your projects dependecies in your package.json
file:
"dependencies": {
"@volvo-cars/content-delivery-client": "workspace:*"
}
Dictionaries
Create Dictionary Client
A single client can be shared across requests to utilize the cache.
import { createClient } from '@volvo-cars/content-delivery-client';
const contentClient = createClient({
applicationId: 'applicationId',
defaultDataSource: 'dotcom-sitecore-prod',
allowedEnvironments: process.env.ALLOW_PREVIEW
? ['authoringPreview', 'live']
: ['live'],
apiKey: process.env.CONTENT_DELIVERY_API_KEY,
revalidate: 120,
forceLocalData: process.env.DEPLOY_ENV === 'dev',
});
You can also keep most configurations as environment variables:
import { createClient } from '@volvo-cars/content-delivery-client';
const contentClient = createClient({
applicationId: 'applicationId',
defaultDataSource: process.env.DEFAULT_CONTENT_DATA_SOURCE,
allowedDataSources: process.env.ALLOWED_CONTENT_DATA_SOURCES,
defaultEnvironment: process.env.DEFAULT_CONTENT_ENVIRONMENT,
allowedEnvironments: process.env.ALLOWED_CONTENT_ENVIRONMENTS,
apiKey: process.env.CONTENT_DELIVERY_API_KEY,
});
getAllDictionaries
Fetches all dictionaries in an application in a single API call.
try {
await contentClient.getAllDictionaries({
locale: 'en',
});
} catch (error) {
}
getDictionary
Fetches a single dictionary.
try {
await contentClient.getDictionary('myNamespace.myDictionaryName', {
locale: 'en',
});
} catch (error) {
}
getDictionaries
Convenience method to fetch multiple dictionaries. Sends one HTTP request per dictionary.
If a one of the dictionaries were missing in the given locale, the dictionaries from the
successful requests will be available as the data
property on the thrown error.
let dictionaries;
try {
dictionaries = await contentClient.getDictionaries(
['myNamespace.myDictionaryName', 'myNamespace.otherDictionary'],
{
locale: 'en',
}
);
} catch (error) {
if (error.name === 'DictionaryNotFoundError' && error.data) {
dictionaries = error.data;
}
}
API
createClient(clientConfig): ContentDeliveryClient
clientConfig
applicationId: string
Id of your applicationapiKey: string
Content Delivery API key for your product.defaultDataSource?: string
Default DataSource to fetch from.revalidate?: number | { dictionaries?: number, entries?: number }
An optional amount in seconds after which to revalidate cached content in the background. Defaults to 60 seconds. Set to 0 to always fetch from the network and only use the cache on errors.fetchOptions?: { agent?: Agent | null }
Options to pass through to fetch
. By default includes a Keep-Alive https Agent in Node.js. Pass agent: null
or a custom https.Agent
instance to disable the default agent.path?: string
File system path where local fallback content is available, if local fallbacks are used.forceLocalData?: boolean
Use content from the local (English) master files, no API calls are made. Useful in development. Defaults to false
. Only available in Node.js.fallbackToLocalData?: boolean
Use local (English) master content if the data is missing in the datasource. Defaults to false
. Only available in Node.js.
ContentDeliveryClient.getAllDictionaries(options: GetOptions): Promise<Dictionaries>
ContentDeliveryClient.getDictionary(canonicalDictionaryName: string, options: GetOptions): Promise<Dictionaries>
ContentDeliveryClient.getDictionaries(canonicalDictionaryNames: string[], options: GetOptions): Promise<Dictionaries>
ContentDeliveryClient.getEntry(canonicalName: string, options: GetOptions): Promise<unknown>
ContentDeliveryClient.listEntries(contentType: ContentType, options: GetOptions): Promise<ListEntriesResponseData>
Dictionaries
All methods return a Dictionaries
object where the key is the Canonical Dictionary Name, and the value is an object of dictionary items.
{
'namespace1.dictionaryName': {
'dictionaryItemKey': 'dictionaryItemValue',
}
}
GetOptions
locale: string
Which locale to fetch content for.environment?: live | authoringPreview | master
: Environment to fetch content from in the CMS. Defaults to live
.preview?: boolean
Fetch preview content from the CMS. Defaults to false
market?: boolean
Fetch content from within a market node.operationId?: string
VCC-Api-OperationId header to send with the request.timeout?: number
Timeout in seconds after which to stop waiting for a response and throw an error instead.dataSource?
Data Source to fetch from. Defaults to defaultDataSource
from the Client Config.
ContentDeliveryError
Base class for all errors and always what's returned from any errors when using the Content Delivery API.
name: string
ContentDeliveryErrorerrors: Error[]
The original runtime error(s).dataSource: string
locale: string
preview: boolean
market?: string
operationId?: string
DictionaryNotFoundError
Thrown if a language version for a dictionary could not be found, or if no dictionaries are available in an application.
name: string
DictionaryNotFoundErrordata?: Dictionaries
Partial data from any sucessful requests.
Caching
Caching follows the Stale While Revalidate and Stale While Error patterns, which means any fetch except the first one will immediately return a response from a local in-memory cache. If the returned content was stale, meaning the revalidate
time has passed since it was last fetched, it will be updated in the background, and on the next fetch the fresh content will be returned.
This means that as long as a single request has ever succeeded in your application the client will continue to return (old, but available) dictionary entries until your server is restarted.
One copy of every entry in every locale in your application will be kept in a memory cache, which should be fine for most use cases. If this becomes a problem you can disable caching by setting revalidate
to 0
. A Least Recently Used cache expiration is on the TODO list.
Usage with React
Nextjs
import mappedItems from '../src/content-management/mapItemsToParameters.json';
import { getDictionariesProvider } from '@vcc-www/react-translate';
export const { useTranslate, DictionariesProvider } = getDictionariesProvider<
typeof mappedItems
>();
import { useTranslate } from '../src/providers/DictionariesProvider';
export const MyComponent: React.FC = () => {
const translate = useTranslate();
return (
<div>
{translate('item1', {
parameter1: 'Some value',
parameter2: 'Some value',
})}
</div>
);
};
import {
createClient,
Dictionaries,
} from '@volvo-cars/content-delivery-client';
import { DictionariesProvider } from '../src/providers/DictionariesProvider';
import { MyComponent } from '../src/components/MyComponent';
const contentClient = createClient({
path: './src/content-management',
apiKey: process.env.CONTENT_DELIVERY_API_KEY,
defaultDataSource: 'dotcom-sitecore-prod',
applicationId: 'myApplication',
forceLocalData: process.env.NODE_ENV === 'development',
});
export default function Index({
dictionaries,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<DictionariesProvider locale="en" dictionaries={dictionaries}>
<MyComponent />
</DictionariesProvider>
);
}
export const getStaticProps: GetStaticProps<{
dictionaries: Dictionaries,
}> = async (props) => {
try {
const dictionaries = await contentClient.getAllDictionaries({
locale: 'en',
});
} catch (error) {
console.log(error);
}
return {
props: {
dictionaries,
},
};
};
Usage with React SSR
import React from 'react';
import ReactDomServer from 'react-dom/server';
import express from 'express';
import { createClient } from '@volvo-cars/content-delivery-client';
import App from './components/App.tsx';
import htmlTemplate from './build/index.html';
const app = express();
const port = 3000;
const contentClient = createClient({
path: './src/content-management',
apiKey: process.env.CONTENT_DELIVERY_API_KEY,
defaultDataSource: 'dotcom-sitecore-prod',
applicationId: 'myApplication',
revalidate: 60,
});
app.get('/', (req, res) => {
const content = ReactDomServer.renderToString(<App />);
let dictionaries = {};
try {
dictionaries = await contentClient.getAllDictionaries({
locale: 'en',
});
} catch (error) {
console.error(error);
}
const html = html.replace('<div id="root"></div>', `
<script type="application/json" id="__DICTIONARIES_STATE__">${JSON.stringify(dictionaries)}</script>
<div id="root">${content}</div>
`);
res.send(html)
});
app.listen(port, () => {
console.info(`Listening on port ${port}`);
});
import React from 'react';
import ReactDOM from 'react-dom'
import App from './components/App.tsx';
import { DictionariesProvider } from './src/providers/DictionariesProvider';
const stateNode = document.getElementById('__DICTIONARIES_STATE__');
const dictionaries = JSON.parse(stateNode.innerHTML);
ReactDOM.hydrate(
<DictionariesProvider locale = "en" dictionaries={dictionaries}>
<App />
</DictionariesProvider>
,document.getElementById('app');
)