Next.js frontend intended to be used with the Rubin EPO craft-cms-template.
- Ensure that the Craft CMS container is running and functioning correctly by going to http://localhost:8080/api, if everything is working fine you should see some message about a missing GraphQL query
- Create a
.env
file in the root of your project based on the.env.local.sample
- Install dependencies
yarn
- Start the dev server
yarn dev
Changed from previous iterations of the template, many atomic components have been removed from this repository in favor of using the @rubin-epo/epo-react-lib package.
Global styles are also imported from this library, although some of the resets and other basic styling have been preserved as SASS inside of this template.
The @rubin-epo/epo-react-lib/styles
styles are added to the application in [locales]/layout
using styled-components
global styles helper and wrapped in a 'use client'
directive. A pre-release build of the EPO React libraries is necessary for this to function.
To work correctly in the app directory, a styles registry consumes the CSS generated by styled-components
, for understanding on how this works see this issue
Following that, styles.scss
is imported in the same page.
import { GlobalStyles } from "@rubin-epo/epo-react-lib/styles"; // styled-components
import styles from "@/styles/styles.scss"; // SASS stuff
const RootLayout = async ({ params: { locale = fallbackLng }, children }) => {
return (
<html lang={locale}>
<head></head> // empty header tag is necessary for styles
<body>
<UIDReset>
<StyledComponentsRegistry>
<GlobalStyles includeFonts={false} />
<GlobalDataProvider>{children}</GlobalDataProvider>
</StyledComponentsRegistry>
</UIDReset>
</body>
</html>
);
};
Components can be imported and used across the application.
import { Container } from "@rubin-epo/epo-react-lib";
export default function Component() {
return <Container />;
}
This application is localized using a combination of i18next, Next.js, and CraftCMS. The following outlines the role each piece plays:
At the top level of the app directory is a [locale]
wildcard folder which will match the first URI segment to determine the locale.
The default locale's key is not shown in the URI, this function along with other locale detection and routing is managed by the next-intl
package's middleware. The middleware will save the current locale to a cookie named NEXT_LOCALE
Example:
/ <-- English homepage
/es <-- Spanish homepage
/surveying-the-solar-system <-- English investigation landing page
/es/surveying-the-solar-system <-- Spanish investigation landing page
The locale URI segment is considered the source of truth for locale in this application.
i18next is set up to work for both server and client components in the app directory. The language config is defined in lib/i18n/settings
.
There is a server useTranslation
hook in lib/i18n
that can be used by server components, this hook requires passing in the namespaces and locale.
import { useTranslation } from `@\lib\i18n`
const Layout = () => {
const { t } = await useTranslation("es", "translation");
return <div>{t("translation:key")}</div>;
};
A singular instance of i18next is put into a provider at the root layout. Client components rendered during the SSR pass and browser pass can access this instance with the useTranslation
hook imported from react-i18next
.
'use client'
import { useTranslation } from `react-i18next`
const Page = () => {
const { t } = useTranslation();
return <div>{t("translation:key")}</div>;
};
All CMS content is localized using CraftCMS. Craft has separate sites defined for each locale that can be accessed during editing. In GraphQL queries, site
is the parameter passed to Craft that will determine which locale to retrieve. For the default locale, the site is named default
and for other locales it uses the two digit locale identifier.
const site = locale === "en" ? "default" : locale;
const uri = `${investigation}/${page}`;
const { data } = await queryAPI({
query: Query,
variables: {
site: [site],
uri: [uri],
},
});
This site uses GraphQL Codegen to generate static types for data sent to and received from Craft's headless API. In short, GraphQL Codegen downloads and stores the schema from the API, then observes request definitions in components and integrates with TypeScript to type the response. Use yarn codegen
to run these two processes. There is also a watcher that can be used, but this isn't set up currently.
Configuration is defined in /codegen.ts
. A secret token is required to fetch any private schemas; this can be generated in the Craft backend and stored as an environment variable. For currently required schema tokens, see /.env.local.sample
.
To avoid type conflicts, GraphQL Codegen should be configured to observe separate files for each schema. The current configuration is to use the public schema by default, then store components that have queries or fragments for private schemas in clearly-marked subfolders (e.g. /components/student-schema/
). See the generates
property in /codegen.ts
for specifics. Components and Server Actions also need to make sure they import the graphql()
function from the correct location; for instance:
import { graphql } from "@/gql/public-schema";
Running the client within a container is a good way to test deployment in a production-like environment.
There is one crucial thing to keep in mind when configuring your local client to run in a Docker container: Docker containers run in an isolated network that have a different concept of the usage of "localhost". As such, despite the fact that the Craft CMS container may be exposed at localhost on port 8080 on the host machine (your laptop), within the client container "localhost" is local to that container. In order for the client container to be able to communicate with the Craft CMS container you need to know the Craft CMS container's gateway IP address.
Luckily, a node.js script is bundled with this code and Docker allows for arguments to be passed into a docker build
command.
In order to build the Docker client image ensure the API project is running then enter the following command in the root project folder:
docker build -t epo/rubin_ui . --build-arg API_IP=$(node getApiGatewayURL) --build-arg API_PORT=8080
As mentioned, if the Craft CMS container project is not running within a Docker container before running the above command then the build may succeed, but will have broken links, images, and missing content.
Finally, once the image is built, create/start the Docker container with the following command:
docker run -p 3000:3000 epo/rubin_ui
For your convenience, a node.js script has been included in this repo that grabs the Craft CMS container Docker gateway IP and logs it to the terminal. Ensure the Craft CMS container is running, then in the terminal enter:
node getApiGatewayURL
- Ensure that the Craft CMS container is running and functioning correctly by going to http://localhost:8080/api, if everything is working fine you should see some message about a missing GraphQL query
- In the terminal, enter the command
docker network ls
and you should she text table output - under the "NAME" column verify that you see one row with the value of the Craft CMS container - Enter the command
docker network inspect <NAME>
- The ouput from the above command will be a JSON object, the gateway IP can be found at: IPAM.Config.Gateway
This IP will change between bringing up and down the container, so keep in mind that you'll need to do this step everytime you bring the Craft CMS containers down and back up.