Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Headless SSR Proxy] Fix shape of config object #979

Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Sitecore JSS for SSR using Experience Edge is considered experimental.
<!---
@TODO: Update to version 20.0.0 docs before release
-->
[Documentation](https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/server-side-render-jss-apps-headlessly-using-a-sitecore-experience-edge-endpoint.html)

[Documentation](https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/server-side-render-jss-apps-headlessly-using-a-sitecore-experience-edge-endpoint.html)

> This is a sample setup that is not officially supported by Sitecore.

Expand All @@ -15,17 +16,17 @@ This is a sample setup showing one of how you can configure rendering server on

1. Your instance needs to be configured with Headless Services Module and the API Key provisioned.

### // TODO: document how to test GraphQL queries
### // TODO: document how to test GraphQL queries

1. Next.js, React, Angular, and Vue samples support Experience Edge out of the box. The GraphQL components and query are compatible with the Experience Edge schema with no further changes necessary. Provide a `sc_apikey` header for authentication, this header is used for both Sitecore XM Edge schema and Sitecore Experience Edge. Refer to the GraphQL Connected demo component in the desired framework.
1. Next.js, React, Angular, and Vue samples support Experience Edge out of the box. The GraphQL components and query are compatible with the Experience Edge schema with no further changes necessary. Provide a `sc_apikey` header for authentication, this header is used for both Sitecore XM Edge schema and Sitecore Experience Edge. Refer to the GraphQL Connected demo component in the desired framework.

1. Build your JS app bundle with `jss build`.

> You can use JSS sample apps which support server side rendering (JSS integrated mode) to operate with this project.
> You can use JSS sample apps which support server side rendering (JSS integrated mode) to operate with this project.

1. Deploy the build artifacts from your app (`/dist` or `/build` within the app) to the `sitecoreDistPath` set in your app's `package.json` under the SSR sample root path. Most apps use `/dist/${jssAppName}`, for example `$ssrSampleRoot/dist/${jssAppName}`.

> Another way to deploy the artifacts to the SSR sample is to change the `instancePath` in your app's `scjssconfig.json` to the SSR sample root path, and then use `jss deploy files` within the app to complete the deployment to the SSR sample.
> Another way to deploy the artifacts to the SSR sample is to change the `instancePath` in your app's `scjssconfig.json` to the SSR sample root path, and then use `jss deploy files` within the app to complete the deployment to the SSR sample.

## Setup

Expand All @@ -35,14 +36,16 @@ Open `config.js` and specify your application bundle and connection settings.

The following environment variables can be set to configure the SSR sample instead of modifying `config.js`, for environments where this is more desirable like containers:

| Parameter | Description |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SITECORE_JSS_APP_NAME` | The JSS app's name. Used when request layout data and dictionary using graphql query and the default value of `SITECORE_JSS_SERVER_BUNDLE` if it's not set. |
| `SITECORE_API_KEY` | The API key provisioned on Sitecore Experience Edge. |
| `SITECORE_JSS_SERVER_BUNDLE` | Path to the JSS app's `server.bundle.js` file. |
| `SITECORE_EXPERIENCE_EDGE_ENDPOINT` | Sitecore Experience Edge endpoint. |
| `DEFAULT_LANGUAGE` | The JSS app's default language. Used to determine language context in case language is not specified in request URL.|
| `PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js). |
| Parameter | Description |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SITECORE_JSS_APP_NAME` | The JSS app's name. Used when request layout data and dictionary using graphql query and the default value of `SITECORE_JSS_SERVER_BUNDLE` if it's not set. |
| `SITECORE_API_KEY` | The API key provisioned on Sitecore Experience Edge. |
| `SITECORE_JSS_SERVER_BUNDLE` | Path to the JSS app's `server.bundle.js` file. |
| `SITECORE_EXPERIENCE_EDGE_ENDPOINT` | Sitecore Experience Edge endpoint. |
| `DEFAULT_LANGUAGE` | The JSS app's default language. Used to determine language context in case language is not specified in request URL. |
| `PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js). |

These environment variables can be set in the .env file located in the root of the app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put this into the statement above the table (it might get lost down here). Something like:

"The following environment variables can be set to configure the proxy instead of modifying config.js. You can use the .env file located in the root of the app or set these directly in the environment (for example, in containers)."


## Build & run

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The setup is using `sitecore-jss-proxy` that enables request proxying to Sitecor
<!---
@TODO: Update to version 20.0.0 docs before release
-->

[Documentation](https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/server-side-render-jss-apps-headlessly-using-the-jss-proxy.html)

> This is a sample setup that is not officially supported by Sitecore.
Expand Down Expand Up @@ -45,6 +46,8 @@ The following environment variables can be set to configure the proxy instead of
| `SITECORE_PATH_REWRITE_EXCLUDE_ROUTES` | Optional. Pipe-separated list of absolute paths that should not be rendered through SSR. Defaults can be seen in [config.js](./config.js). |
| `SITECORE_ENABLE_DEBUG` | Optional. Writes verbose request info to stdout for debugging. Defaults to `false`. |

These environment variables can be set in the .env file located in the root of the app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (put into statement above the table).


## Build & run

1. Run `npm install`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,50 @@
const mcache = require('memory-cache');

// List of urls that will be skipped during caching
const EXCLUDED_PATHS = [
'layouts/system',
'sitecore/api/jss/dictionary',
'/sitecore/api/layout'
]
const EXCLUDED_PATHS = ['layouts/system', 'sitecore/api/jss/dictionary', '/sitecore/api/layout'];

/**
* @param {string} method request method
* @param {string} url request url
* @returns {boolean} is path excluded
*/
const isExcludedPath = (method, url) => {
const containsExcludedPath = !!EXCLUDED_PATHS.find(path => url.includes(path));
const containsExcludedPath = !!EXCLUDED_PATHS.find((path) => url.includes(path));

return method !== 'GET' || containsExcludedPath;
}
return method !== 'GET' || containsExcludedPath;
};

/**
* Cache requests during {@link duration} that aren't excluded in {@link EXCLUDED_PATHS}
* @param {number} duration The number of milliseconds to cache request
*/
const cacheMiddleware = (duration = 10000) => (req, res, next) => {
if (isExcludedPath(req.method, req.originalUrl)) return next();

const key = '__proxy_cache__' + req.originalUrl || req.url
const cachedBody = mcache.get(key)

if (cachedBody) {
res.send(cachedBody);
return;
}

const { end: _end, write: _write } = res;
let buffer = new Buffer.alloc(0);
// Rewrite response method and get the content.
res.write = data => {
buffer = Buffer.concat([buffer, data]);
};
res.end = () => {
const body = buffer.toString();
mcache.put(key, body, duration);
_write.call(res, body);
_end.call(res);
};

next()
}

module.exports = cacheMiddleware;
if (isExcludedPath(req.method, req.originalUrl)) return next();

const key = '__proxy_cache__' + req.originalUrl || req.url;
const cachedBody = mcache.get(key);

if (cachedBody) {
res.send(cachedBody);
return;
}

const { end: _end, write: _write } = res;
let buffer = Buffer.alloc(0);

// Rewrite response method and get the content.
res.write = (data) => {
buffer = Buffer.concat([buffer, data]);
};

res.end = () => {
const body = buffer.toString();
mcache.put(key, body, duration);
_write.call(res, body);
_end.call(res);
};

next();
};

module.exports = cacheMiddleware;
31 changes: 29 additions & 2 deletions packages/sitecore-jss-proxy/src/ProxyConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { IncomingMessage, ServerResponse } from 'http';
import { Config as HttpProxyConfig } from 'http-proxy-middleware';
import { AppRenderer } from './AppRenderer';
import { RenderResponse } from './RenderResponse';
import { RouteUrlParser } from './RouteUrlParser';

/** A reply from the Sitecore Layout Service */
export interface LayoutServiceData {
sitecore: {
context: {
[key: string]: unknown;
language?: string;
site?: {
name?: string;
};
};
};
}

export interface ProxyConfig {
/** Hostname to proxy to (i.e. Sitecore CD server 'http://siteco.re') */
Expand Down Expand Up @@ -30,7 +45,13 @@ export interface ProxyConfig {
onError?: (
error: Error,
response: IncomingMessage
) => Promise<{ statusCode?: number; content?: string }>;
) =>
| null
| {
statusCode?: number;
content?: string;
}
| Promise<{ statusCode?: number; content?: string }>;
/** Enables transforming SSR'ed HTML after it is rendered, i.e. to replace paths. */
transformSSRContent?: (
response: RenderResponse,
Expand All @@ -42,7 +63,7 @@ export interface ProxyConfig {
request: IncomingMessage,
response: ServerResponse,
proxyResponse: IncomingMessage,
layoutServiceData: { [key: string]: unknown }
layoutServiceData: LayoutServiceData
) => // eslint-disable-next-line @typescript-eslint/ban-types
Promise<object>;
/** Hook to alter HTTP headers in a custom way. */
Expand All @@ -53,4 +74,10 @@ export interface ProxyConfig {
) => void;
/** Responses from the proxy greater than this size (in bytes) are rejected. */
maxResponseSizeBytes?: number;
/** The require'd server.bundle.js file from your pre-built JSS app */
serverBundle?: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it required, and fix unit tests

[key: string]: unknown;
renderView: AppRenderer;
parseRouteUrl: RouteUrlParser;
};
}
6 changes: 3 additions & 3 deletions packages/sitecore-jss-proxy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import HttpStatus from 'http-status-codes';
import setCookieParser, { Cookie } from 'set-cookie-parser';
import zlib from 'zlib'; // node.js standard lib
import { AppRenderer } from './AppRenderer';
import { ProxyConfig } from './ProxyConfig';
import { ProxyConfig, LayoutServiceData } from './ProxyConfig';
import { RenderResponse } from './RenderResponse';
import { RouteUrlParser } from './RouteUrlParser';
import { buildQueryString, tryParseJson } from './util';
Expand Down Expand Up @@ -257,7 +257,7 @@ async function renderAppToResponse(
/**
* @param {object} layoutServiceData
*/
async function createViewBag(layoutServiceData: { [key: string]: unknown }) {
async function createViewBag(layoutServiceData: LayoutServiceData) {
let viewBag = {
statusCode: proxyResponse.statusCode,
dictionary: {},
Expand Down Expand Up @@ -298,7 +298,7 @@ async function renderAppToResponse(
viewBag
);
} catch (error) {
return replyWithError(error);
return replyWithError(error as Error);
}
};
}
Expand Down