diff --git a/app/(storyblok)/[[...slug]]/page.tsx b/app/(storyblok)/[[...slug]]/page.tsx index 73ab7dff..ffac720d 100644 --- a/app/(storyblok)/[[...slug]]/page.tsx +++ b/app/(storyblok)/[[...slug]]/page.tsx @@ -69,7 +69,16 @@ export async function generateStaticParams() { // Use the `cdn/links` endpoint to get a list of all stories without all the extra data. const response = await storyblokApi.getAll('cdn/links', sbParams); - const stories = response.filter((link) => link.is_folder === false); + + // Filters + let stories = response; + // Filter out folders. + stories = response.filter((link) => link.is_folder === false); + // Filter out test content by filtering out the `test` folder. + stories = stories.filter((link) => !link.slug.startsWith(getSlugPrefix() + '/test')); + // Filter out globals by filtering out the `global-components` folder. + stories = stories.filter((link) => !link.slug.startsWith(getSlugPrefix() + '/global-components')); + let paths: PathsType[] = []; stories.forEach((story) => { diff --git a/next.config.js b/next.config.mjs similarity index 51% rename from next.config.js rename to next.config.mjs index 10d8a7a4..db483981 100644 --- a/next.config.js +++ b/next.config.mjs @@ -1,5 +1,10 @@ +import { getStoryblokRedirects } from './utilities/data/getStoryblokRedirects.mjs'; + /** @type {import('next').NextConfig} */ const nextConfig = { + eslint: { + dirs: ['app', 'components', 'contexts', 'hooks', 'pages', 'services', 'utilities'], + }, images: { remotePatterns: [ { @@ -13,6 +18,10 @@ const nextConfig = { CONTEXT: process.env.CONTEXT, STORYBLOK_SLUG_PREFIX: process.env.STORYBLOK_SLUG_PREFIX, }, + async redirects() { + const storyblokRedirects = await getStoryblokRedirects(); + return storyblokRedirects; + }, }; -module.exports = nextConfig; +export default nextConfig; diff --git a/utilities/data/getStoryblokRedirects.mjs b/utilities/data/getStoryblokRedirects.mjs new file mode 100644 index 00000000..0b11f8be --- /dev/null +++ b/utilities/data/getStoryblokRedirects.mjs @@ -0,0 +1,111 @@ +import { apiPlugin, getStoryblokApi, storyblokInit } from '@storyblok/react/rsc'; + +storyblokInit({ + accessToken: process.env.STORYBLOK_ACCESS_TOKEN, + use: [apiPlugin], + apiOptions: { + region: 'us', + }, +}); + +/** + * Get the slug prefix for Storyblok. + * @returns {string} + */ +const getSlugPrefix = () => { + const prefix = process.env.STORYBLOK_SLUG_PREFIX || 'momentum'; + // Ensure there is no trailing slash. + if (prefix.slice(-1) === '/') { + return prefix.slice(-1); + } + return prefix; +}; + +/** + * Check if the current environment is production. + * @returns {boolean} + */ +const isProduction = () => { + return process.env.CONTEXT === 'production'; +}; + +/** + * Sanitize the redirect code to ensure it is a valid HTTP status code. + * @param {string} code + * @returns {number} + */ +const sanitizeRedirectCode = (code) => { + // Ensure code is a number and in one of 301, 302, 303, 307, 308 + const statusCode = parseInt(code, 10); + if ([301, 302, 303, 307, 308].includes(statusCode)) { + return statusCode; + } + return 301; +}; + +/** + * Double-slash escape characters in a string. + * @param {*} str + * @returns {string} + */ +const sanitizeSourcePath = (str) => { + const parts = str.split('?'); + const path = parts[0]; + return path; +}; + +/** + * Extract query parameters from a URL. + * @param {string} url + * @returns {Array} + */ +const extractQueryParameters = (url) => { + // Parse the URL and get the query string + const urlObj = new URL(url, 'http://example.com'); // base URL needed for relative paths + const queryParams = new URLSearchParams(urlObj.search); + + // Create an array to hold the query parameter objects + const queryArray = []; + + // Iterate through each query parameter and add it to the array in the required format + queryParams.forEach((value, key) => { + queryArray.push({ + type: 'query', + key: key, + value: value, + }); + }); + + return queryArray; +}; + +/** + * Get redirects from Storyblok. + * @returns {Promise} + */ +export const getStoryblokRedirects = async () => { + const storyblokApi = getStoryblokApi(); + const isProd = isProduction(); + const sbParams = { + version: isProd ? 'published' : 'draft', + resolve_links: '0', + resolve_assets: 0, + per_page: 100, + starts_with: getSlugPrefix() + '/global-components/redirects', + }; + + const stories = await storyblokApi.getAll(`cdn/stories`, sbParams); + + if (!stories || stories.length === 0) { + return []; + } + + const redirects = stories.map(entry => ({ + source: sanitizeSourcePath(entry.content.from), + has: extractQueryParameters(entry.content.from), + destination: entry.content.to, + statusCode: sanitizeRedirectCode(entry.content.statusCode), + })); + + return redirects; +};