> NextJS adapter with Guillotine support and basic views
npm install @enonic/nextjs-adapter> NextJS adapter with Guillotine support and basic views
``bash`
npm i --save @enonic/nextjs-adapter
All functions and views are split into 4 categories by usage:
- @enonic/nextjs-adapter -- can be used both on server and client sides@enonic/nextjs-adapter/server
- -- can only be used on server side@enonic/nextjs-adapter/client
- -- can only be used on client side@enonic/nextjs-adapter/views/...
- -- views folder, every view has its own file with a default export
Here's the usage example in your project:
`tsx
// server-side functions are accessible at /server
import {fetchContent} from "@enonic/nextjs-adapter/server";
// views are placed in a folder called views and are default exports in files
import MainView from "@enonic/nextjs-adapter/views/MainView"
// ...
const props = await fetchContent(path, {
contentPath: '/content/path',
locale: 'en',
});
// ...
return
`
They are available at @enonic/nextjs-adapter/server
#### fetchContent(contentPath: string | string[], context: Context) => Promise
Fetches the content/component by path with component queries optimization, page structure as well as runtime info calculation. This is the
main method for querying content.
| Argument | Description |
|----------|-------------------|
| context | Execution context |
Usage:
`tsx
import {fetchContent} from '@enonic/nextjs-adapter/server';
import {headers} from 'next/headers';
const context = {
contentPath: '/current/content/path',
locale: 'en',
headers: headers(),
};
const response = fetchContent(context);
`
Response type:
`tsx`
type FetchContentResult = {
error?: {
code: string,
message: string
} | null;
data: Record
common: Record
meta: MetaData, // Runtime information
page: PageComponent | null, // The structure of the page (not present when rendering single component)
};
#### fetchGuillotine(apiUrl: string, mapping: LocaleMapping, options?: FetchOptions) => Promise
Makes a custom request to Guillotine. Used by fetchContent() method.
| Argument | Description |
|-----------|---------------------------------------------------|
| apiUrl | Guillotine API URL |mapping
| | Mapping locale to project |options
| | Request and Next.js config options _(Optional)_ |
Usage:
`tsx
import {fetchGuillotine} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';
const apiUrl = 'http://domain:8000/graphql/api';
const mapping = getLocaleMapping({
contentPath: '/current/content/path',
});
const body = {
query: 'qraphql query as string',
variables: {
foo: 'bar',
},
};
const headers = {
'custom-header': 'header-value',
};
const opts = {
body,
headers,
method: 'GET',
next: {
revalidate: 60,
tags: ['tag1', 'tag2'],
}
};
const result = fetchGuillotine(apiUrl, mapping, opts);
`
Response type:
`tsx`
type GuillotineResult = {
error?: {
code: string,
message: string
} | null;
[dataKey: string]: any;
};
#### fetchFromApi(apiUrl: string, mapping: LocaleMapping, options?: FetchOptions) => Promise
Makes custom third-party service request. Used by the fetchGuillotine() method.
| Argument | Description |
|-----------|---------------------------------------------------|
| apiUrl | Service API URL |mapping
| | Mapping locale to project |options
| | Request and Next.js config options _(Optional)_ |
Usage:
`tsx
import {fetchFromApi} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';
const apiUrl = 'http://domain:8000/graphql/api';
const mapping = getLocaleMapping({
contentPath: '/current/content/path',
});
const body = {
query: 'qraphql query as string',
variables: {
foo: 'bar',
},
}
const headers = {
'custom-header': 'header-value',
}
const opts = {
body,
headers,
method: 'POST',
next: {
revalidate: 60,
tags: ['tag1', 'tag2'],
}
};
const result = fetchFromApi(apiUrl, mapping, opts);
`
#### fetchContentPathsForAllLocales(path: string, query?: string, count?: number) => Promise
Loads all content paths for all locales. Generally used for static site generation.
| Argument | Description |
|----------|---------------------------------------------------|
| path | Root content path |query
| | Request and Next.js config options _(Optional)_ * |count
| | Max result count _(Optional, defaults to 999)_ |
* Default query gets up to count results, sorts them by modifiedTime and excludes following content types:
``
"base:shortcut",
"portal:fragment",
"portal:template-folder",
"portal:page-template",
"media:*"
Usage:
`tsx
import {fetchContentPathsForAllLocales} from '@enonic/nextjs-adapter/server';
const contentPaths = fetchContentPathsForLocale('/site-name', 'custom graphql query', 1001);
`
#### fetchContentPathsForLocale(path: string, mapping: LocaleMapping, query?: string, count?: number) => Promise
Loads all content paths for the current locale. Generally used for static site generation.
Used by fetchContentPathsForAllLocales()
| Argument | Description |
|-----------|-----------------------------------------------------------------------------------------------------|
| path | Root content path |mapping
| | Mapping locale to project |query
| | Request and Next.js config options _(Optional, default value here)_ |count
| | Max result count _(Optional, defaults to 999)_ |
Usage:
`tsx
import {fetchContentPathsForLocale} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';
const mapping = getLocaleMapping({
contentPath: '/current/content/path',
});
const contentPaths = fetchContentPathsForLocale('/', mapping, 'custom graphql query', 1001);
`
They are available at @enonic/nextjs-adapter/client
Create a React.js context that allows child elements to access and modify current locale as well as localize static texts for current
locale. See useLocaleContext() method for example.
| Argument | Type | Description |
|----------|----------|------------------------------|
| locale | String | Set the initial locale value |
Usage:
`tsx
import {LocaleContextProvider} from '@enonic/nextjs-adapter/client';
`
#### useLocaleContext() => LocaleContextType
Methods to access and modify current locale as well as localize static texts, where LocaleContextType is:
`tsx`
interface LocaleContextType {
dictionary: Dict
locale: string
localize: (key: string, ...args: any[]) => string
setLocale: (locale: string) => Promise
}
Usage:
`tsx
'use client';
import {useLocaleContext} from '@enonic/nextjs-adapter/client';
export default function ClientSideComponent() {
const {locale, localize, setLocale} = useLocaleContext();
console.log(Current locale: ${locale});New locale: ${locale}
const localizedText = localize('text.key');
setLocale('en').then((dict) => {
console.log();`
});
// ...
}
They are available at @enonic/nextjs-adapter
#### getUrl(url: string, meta: MetaData) => string
Converts a site-relative or absolute URL to relative one for current viewer (Next.js/Enonic XP). Also takes care of locale if needed.
> INFO: For your URLs to work both in Enonic XP and Next.js you need to:
> 1. Query site-relative or absolute URLs from guillotine
> 2. Wrap them with getUrl() function in the views
| Argument | Description |
|----------|---------------------------------------------------------|
| url | URL you want to transform |meta
| | Runtime data returned by fetchContent |
Usage:
`tsx
import {getUrl} from '@enonic/nextjs-adapter';
const urlRelativeToViewer = getUrl('/some/content/url', meta);
`
#### getAsset(url: string, meta: MetaData) => string
Converts a local asset URL to relative one for current viewer (Next.js/Enonic XP). It doesn't append locales unlike the getUrl().
> INFO: For your URLs to work both in Enonic XP and Next.js you need to:
> 1. Use relative URL to local asset
> 2. Wrap them with getAsset() function in the views
| Argument | Description |
|----------|---------------------------------------------------------|
| url | asset URL you want to transform |meta
| | Runtime data returned by fetchContent |
Usage:
`tsx
import {getAsset} from '@enonic/nextjs-adapter';
const urlRelativeToViewer = getAsset('/some/asset/url', meta);
`
#### richTextQuery(fieldName: string) => string
This is a utility function for querying for RichTextData needed for RichTextView. It creates a graphql query string for
HTML area input type with given field name.
| Argument | Description |
|-------------|----------------------|
| fieldName | HTML area field name |
Usage:
`tsx
import {richTextQuery} from '@enonic/nextjs-adapter';
const query = query($path:ID!){
guillotine {
get(key:$path) {
_path
type
${richTextQuery('htmlField')}
}
}
};`
#### validateData(props: FetchContentResult) => void
Utility method to validate data returned by fetchContent() method. Throws an error or notFound() if data is invalid. Also prevents shortcut content types from being rendered in preview/edit modes.
| Argument | Description |
|----------|-----------------------------|
| props | FetchContentResult object |
Usage:
`tsx
import {fetchContent} from '@enonic/nextjs-adapter/server';
import {validateData} from '@enonic/nextjs-adapter';
const data = fetchContent({
contentPath: '/path/to/content',
locale: 'en',
});
validateData(data);
`
Registry containing definitions of all components (i.e. pages, parts, layouts, macros, etc. ). It is used in the runtime by nextjs-adapter
to make component queries and render components. It has several public methods:
#### static setCommonQuery(query: SelectedQueryMaybeVariablesFunc): void
Sets up a common query that is going to be executed along with component queries and passed to every component on the page.
| Argument | Description |
|----------|---------------------------------------|
| query | Common query definition |
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const query = {
query: 'graphql query',
variables: (path, context, config) => {
return {
path: path + '/some/processing'
};
}
}
ComponentRegistry.setCommonQuery(query);
`
#### static getCommonQuery(): SelectedQueryMaybeVariablesFunc
Gets the common query definition.
`tsx`
type SelectedQueryMaybeVariablesFunc =
string |
QueryGetter |
{
query: string | QueryGetter,
variables: VariablesGetter
} |
[string | QueryGetter, VariablesGetter];
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const query = ComponentRegistry.getCommonQuery();
`
#### static getByComponent(component: PageComponent): ComponentDefinition | undefined
Gets component definition from the ComponentRegistry.
| Argument | Description |
|-------------|--------------------------------------|
| component | Page component to get definition for |
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const definition = ComponentRegistry.getByComponent(component);
`
`tsx`
interface ComponentDefinition {
catchAll?: boolean; // set automatically depending on the binding
query?: SelectedQueryMaybeVariablesFunc,
configQuery?: string,
processor?: DataProcessor,
view?: React.FunctionComponent
}
#### static addMacro(name: string, obj: ComponentDefinition): void
#### static addPart(name: string, obj: ComponentDefinition): void
#### static addLayout(name: string, obj: ComponentDefinition): void
#### static addCPage(name: string, obj: ComponentDefinition): void
#### static addContentType(name: string, obj: ComponentDefinition): void
#### static addComponent(name: string, obj: ComponentDefinition): void
Saves the component definition in ComponentRegistry by name.
> NOTE: addComponent is used for defining general types of Enonic XP components by nextjs-adapter so you don't
> need to do it manually. Overriding default setup may break Enonic XP integration!
| Argument | Description |
|----------|-----------------------------------|
| name | Component name |obj
| | Component definition |
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const definition = {}
ComponentRegistry.addMacro('macro-name', definition);
`
#### static getMacro(name: string): ComponentDefinition | undefined
#### static getPart(name: string): ComponentDefinition | undefined
#### static getLayout(name: string): ComponentDefinition | undefined
#### static getPage(name: string): ComponentDefinition | undefined
#### static getContentType(name: string): ComponentDefinition | undefined
#### static getComponent(name: string): ComponentDefinition | undefined
Gets the component definition stored in ComponentRegistry by its name.
> NOTE: Read addComponent note before using getComponent.
| Argument | Description |
|----------|----------------|
| name | Component name |
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const definition = ComponentRegistry.getMacro('macro-name');
`
Response type: component definition
#### static getMacros(): ComponentDefinition[]
#### static getParts(): ComponentDefinition[]
#### static getLayouts(): ComponentDefinition[]
#### static getPages(): ComponentDefinition[]
#### static getContentTypes(): ComponentDefinition[]
#### static getComponents(): ComponentDefinition[]
Gets all component definitions stored in ComponentRegistry.
> NOTE: Read addComponent note before using getComponents.
Usage:
`tsx
import {ComponentRegistry} from '@enonic/nextjs-adapter';
const macros = ComponentRegistry.getMacros();
`
Response type: List of component definitions
Helper singleton for processing URLs.
#### static process(url: string, meta: MetaData, serverSide = false, isResource = false): string
Processes the absolute URL to become relative for the current viewer, while keeping in mind Next.js assets and Enonic XP binary content
links
> NOTE: There are convenience aliases to this function called getUrl() and getAsset()
| Argument | Description |
|--------------|------------------------------------------------------------------------------|
| url | Absolute URL |meta
| | Runtime data returned by fetchContent |serverSide
| | Whether URL is going to be used on the server side _(Skips adding basePath)_ |isResource
| | Whether URL is a resource _(Skips adding locale)_ |
Usage:
`tsx
import {UrlProcessor} from '@enonic/nextjs-adapter';
const url = UrlProcessor.process('http://www.some.site.com/url/to/content', meta, true, false);
`
#### static processSrcSet(srcset: string, meta: MetaData): string
Processes the image srcset attribute to transform each URL with process method
| Argument | Description |
|----------|---------------------------------------------------------|
| srcset | Value of the srcset attribute |meta
| | Runtime data returned by fetchContent |
Usage:
`tsx
import {UrlProcessor} from '@enonic/nextjs-adapter';
const url = UrlProcessor.processSrcSet('
`
#### static setSiteKey(key: string): void
> DEPRECATED: It is automatically done by nextjs-adapter now and have no effect.
Sets the site key value that is needed for correct absolute URL processing.
| Argument | Description |
|----------|-------------|
| key | Site key |
Usage:
`tsx
import {UrlProcessor} from '@enonic/nextjs-adapter';
UrlProcessor.setSiteKey('
`
#### static isMediaLink(ref: string, linkData: LinkData[]): boolean
Checks if link data array contains link with provided ref. Positive result means that this is a link to Enonic XP content.
> NOTE: link data array is contained in response of the query generated by richTextQuery('fieldName')
| Argument | Description |
|------------|-----------------|
| ref | Link ref |linkData
| | Link data array |
Usage:
`tsx
import {UrlProcessor} from '@enonic/nextjs-adapter';
UrlProcessor.isMediaLink('', linkData);
`
#### static isContentImage(ref: string, imageData: ImageData[]): boolean
Checks if image data array contains image with provided ref. Positive response means that this is an Enonic XP image.
> NOTE: image data array is contained in response of the query generated by richTextQuery('fieldName')
| Argument | Description |
|-------------|------------------|
| ref | Image ref |imageData
| | Image data array |
Usage:
`tsx
import {UrlProcessor} from '@enonic/nextjs-adapter';
UrlProcessor.isContentImage('
`
In order to create association between locale and Enonic XP project, ENONIC_MAPPINGS environment variable should be set.@enonic/nextjs-adapter
Functions for reading those mappings are available at .
#### getLocaleMapping(context: Context): LocaleMapping
| Argument | Description |
|-----------|------------------|
| context | Context object |
Usage:
`tsx
import {getLocaleMapping} from '@enonic/nextjs-adapter';
const mapping = getLocaleMapping({
contentPath: '/current/content/path',
locale: 'en',
});
`
`tsx`
export interface LocaleMapping {
default: boolean
project: string
site: string
locale: string
}
#### getLocaleMappingByProjectId(projectId?: string, useDefault = true): LocaleMapping
| Argument | Description |
|--------------|------------------------------------------------------------|
| projectId | Enonic project ID _(Optional)_ |useDefault
| | Use default locale as fallback _(Optional, default: true)_ |
Usage:
`tsx
import {getLocaleMappingByProjectId} from '@enonic/nextjs-adapter';
const mapping = getLocaleMappingByProjectId('project-id', true);
`
#### getLocaleMappingByLocale(locale?: string, useDefault = true): LocaleMapping
| Argument | Description |
|--------------|------------------------------------------------------------|
| locale | locale ID _(Optional)_ |useDefault
| | Use default locale as fallback _(Optional, default: true)_ |
Usage:
`tsx
import {getLocaleMappingByLocale} from '@enonic/nextjs-adapter';
const mapping = getLocaleMappingByLocale('en', true);
`
#### getRequestLocaleInfo(context: Context): LocaleMapping
Attempts to get the locale info from the request. Along with that it also returns the default locale and all configured locales.
| Argument | Description |
|-----------|------------------|
| context | Context object |
Usage:
`tsx
import {getRequestLocaleInfo} from '@enonic/nextjs-adapter';
const mapping = getRequestLocaleInfo({
contentPath: '/current/content/path',
locale: 'en',
});
`
Response type:
`tsx`
interface LocaleInfo {
locale: string,
locales: string[],
defaultLocale: string
}
These functions can be used both on server and client sides, but it is recommended to
use native React.js context classes for client-side localization.
#### static I18n.setLocale(locale: string): Promise
Sets the current locale and returns the dictionary for it.
| Argument | Description |
|----------|----------------------|
| locale | Locale id, i.e. en |
Usage:
`tsx
import {I18n} from '@enonic/nextjs-adapter';
const dict = await I18n.setLocale('en');
`
`tsx`
interface Dict {
[key: string]: string
}
#### static I18n.localize(key: string, ...args: any[]): string
Localizes the static text by key and replaces placeholders with provided arguments.
| Argument | Description |
|-----------|--------------------|
| key | text key |...args
| | template arguments |
Usage:
`tsx
import {I18n} from '@enonic/nextjs-adapter';
const localizedText = I18n.localize('text.key', 'value1', 'value2');
`
#### static I18n.getLocale(): string
Returns the current locale.
Usage:
`tsx
import {I18n} from '@enonic/nextjs-adapter';
const locale = I18n.getLocale();
`
#### static I18n.getDictionary(): Dict
Returns dictionary for the current locale.
Usage:
`tsx
import {I18n} from '@enonic/nextjs-adapter';
const dict = I18n.getDictionary();
`
There is also a number of utility constants and functions available at @enonic/nextjs-adapter.
`tsx
import {CATCH_ALL} from './constants';
IS_DEV_MODE; // True if current mode == development
APP_NAME; // Name of the app defined in .env files
APP_NAME_UNDERSCORED; // APP_NAME with underscores instead of dots
APP_NAME_DASHED; // APP_NAME with dashes instead of dots
CATCH_ALL; // Catch all component name
PORTAL_COMPONENT_ATTRIBUTE; // Portal component attribute name
PORTAL_REGION_ATTRIBUTE; // Portal region attribute name
JSESSIONID_HEADER; // JSESSIONID header name
PROJECT_ID_HEADER; // Project ID header name
RENDER_MODE_HEADER; // Render mode header name
XP_BASE_URL_HEADER; // XP base URL header name
enum XP_REQUEST_TYPE { // Enum for XP request types
COMPONENT = 'component',
TYPE = 'type',
PAGE = 'page',
}
enum RENDER_MODE { // Enum for render modes
INLINE = 'inline',
EDIT = 'edit',
PREVIEW = 'preview',
LIVE = 'live',
ADMIN = 'admin',
NEXT = 'next',
}
enum XP_COMPONENT_TYPE { // Enum for XP component types
PART = 'part',
LAYOUT = 'layout',
TEXT = 'text',
FRAGMENT = 'fragment',
PAGE = 'page',
}
// Sanitizes text according to graphql naming spec http://spec.graphql.org/October2021/#sec-Names
const sanitizeGraphqlName = (text: string) => string;
// Returns full content api URL with current project and branch appended
const getContentApiUrl = (context: Context) => string;
`
They are located in @enonic/nextjs-adapter/views folder. Each view is a default export in the corresponding file.
####
The main view of the application. It accepts the result of fetchContent method and is intended to be used as a view for your next.js route
| Argument | Type | Description |
|----------|-----------------------------------|-----------------------------|
| common | Record | Result of the common query |data
| | Record | Result of the graphql query |page
| | PageComponent | null | Page structure |meta
| | MetaData | Runtime info |
Usage:
`tsx
import MainView from '@enonic/nextjs-adapter/views/MainView';
import fetchContent from '@enonic/nextjs-adapter';
export async function getServerSideProps(context: Context) {
const props = fetchContent('/content/path', context);
return {
props
}
}
export default MainView;
`
####
Tag for disabling client side hydration if the condition is true. This will remove interactivity from children.
| Argument | Type | Description |
|--------------------|-----------|------------------------------------|
| condition = true | Boolean | Condition to trigger static output |tag = 'div'
| | String | Html tag to use for static output |
Usage:
`tsx
import StaticContent from '@enonic/nextjs-adapter/views/StaticContent';
`
Tag for displaying contents of html area input types. Takes care of processing macros, URLs and images inside.
| Argument | Type | Description |
|--------------------------------|----------------|---------------------------------------------------------------------------------------------------|
| data | RichTextData | Rich text data |meta
| | MetaData | Runtime data returned by fetchContent method. |customReplacer
| | Replacer | Function to do custom element processing. Not invoked for image, link and macro nodes. _Optional_ |className
| | String | Class name to add to the root html element. _Optional_ |renderMacroInEditMode = true
| | boolean | Flag passed to macros telling if they should render themselves in edit mode |tag = 'div'
| | String | Html tag to use as a root |
> TIP! There is a utility function richTextQuery(fieldName) generating part of the graphql query to
> obtain RichTextData for html area input types.
Usage:
`tsx
import RichTextView from '@enonic/nextjs-adapter/views/RichTextView';
`
####
Tag for rendering page regions. It is useful when implementing custom page views.
All necessary data can be acquired by running fetchContent()
| Argument | Type | Description |
|----------|-------------------------|-------------------------------------|
| page | PageData | null; | Page structure |meta
| | MetaData | Runtime info |name
| | String | Render only this region. _Optional_ |common
| | any | Result of common query. _Optional_ |
Usage:
`tsx
import StaticContent from '@enonic/nextjs-adapter/views/StaticContent';
``