diff --git a/app/.storybook/main.js b/app/.storybook/main.js index 4fbc1680..92140790 100644 --- a/app/.storybook/main.js +++ b/app/.storybook/main.js @@ -19,9 +19,9 @@ const config = { builder: { // Cache build output between runs, to speed up subsequent startup times fsCache: true, - // Applies in development mode. Storybook will start up faster, at the cost - // of slightly slower browsing time when you navigate to another story. - lazyCompilation: true, + // lazyCompilation breaks Storybook when running from within Docker + // Google Translate this page for context: https://zenn.dev/yutaosawa/scraps/7764e5f17173d1 + lazyCompilation: false, }, }, }, diff --git a/app/.storybook/preview.js b/app/.storybook/preview.js index 8175ccf9..d75bcc90 100644 --- a/app/.storybook/preview.js +++ b/app/.storybook/preview.js @@ -2,7 +2,7 @@ import i18nConfig from "../next-i18next.config"; // Apply global styling to our stories -import "../styles/styles.scss"; +import "../src/styles/styles.scss"; // Import i18next config. import i18n from "./i18next.js"; diff --git a/app/Dockerfile b/app/Dockerfile index 2ba5f096..aa691343 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,31 +1,83 @@ -# Use the Active LTS version of Node. -# See https://nodejs.org/en/about/releases/ -FROM node:16-alpine -# Keep container packages up-to-date. -# -U runs both apk update and apk upgrade. -RUN apk -U upgrade - - -# setup user stuff -ARG RUN_UID -ARG RUN_USER - -# The following logic creates the RUN_USER home directory and the directory where -# we will be storing the application in the image. This runs when the user is not root -RUN : "${RUN_USER:?RUN_USER and RUN_UID need to be set and non-empty.}" && \ - [ "${RUN_USER}" = "root" ] || \ - (addgroup -S "${RUN_USER}" && adduser -S -G "${RUN_USER}" -h "/home/${RUN_USER}" -u ${RUN_UID} "${RUN_USER}" \ - && mkdir /app \ - && chown -R "${RUN_USER}:${RUN_USER}" "/home/${RUN_USER}" /app) - -USER ${RUN_USER} +# This file is largely based on the template-application-flask Dockerfile and +# Next.js Docker example: https://github.com/vercel/next.js/blob/canary/examples/with-docker-compose +# ============================================================================= +FROM node:18-alpine AS base WORKDIR /app -# Copy all the application files (ignoring files in .dockerignore) to the working directory. -COPY --chown="${RUN_USER}:${RUN_USER}" . /app -# Install application dependencies. -RUN npm ci -# Build the application. -RUN npm run build -# Run the application. -CMD ["npm", "run", "start"] +# Install dependencies +COPY package.json package-lock.json ./ +COPY public ./public +COPY scripts ./scripts +RUN npm ci --no-audit + +# ============================================================================= +# Development stage +# ============================================================================= +FROM base AS dev +WORKDIR /app + +COPY tsconfig.json . +COPY *.config.js . +COPY *.d.ts . +COPY src ./src +COPY stories ./stories +COPY .storybook ./.storybook + +ENV NEXT_TELEMETRY_DISABLED 1 + +CMD ["npm", "run", "dev"] + +# ============================================================================= +# Release stage +# ============================================================================= + +# Build the Next.js app +# ===================================== +FROM base AS builder +WORKDIR /app + +COPY tsconfig.json . +COPY *.config.js . +COPY *.d.ts . +COPY src ./src + +# Environment variables must be present at build time +# https://github.com/vercel/next.js/discussions/14030 +# ARG ENV_VARIABLE +# ENV ENV_VARIABLE=${ENV_VARIABLE} +# ARG NEXT_PUBLIC_ENV_VARIABLE +# ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE} + +ENV NEXT_TELEMETRY_DISABLED 1 + +# Skip lint because it should have happened in the CI already +RUN npm run build -- --no-lint + +# Run the Next.js server +# ===================================== +FROM base AS release +WORKDIR /app + +# Don't run production as root +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +USER nextjs + +COPY --from=builder /app/public ./public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Environment variables must be redefined at run time +# ARG ENV_VARIABLE +# ENV ENV_VARIABLE=${ENV_VARIABLE} +# ARG NEXT_PUBLIC_ENV_VARIABLE +# ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE} +ENV NEXT_TELEMETRY_DISABLED 1 +ENV PORT 3000 + +EXPOSE 3000 + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/app/Makefile b/app/Makefile index eca5f68c..42e09670 100644 --- a/app/Makefile +++ b/app/Makefile @@ -22,6 +22,9 @@ endif export RUN_USER export RUN_UID +################################################## +# Release +################################################## release-build: docker buildx build \ --platform=linux/amd64 \ @@ -29,3 +32,17 @@ release-build: --build-arg RUN_UID=$(RUN_UID) \ $(OPTS) \ . + +################################################## +# Local development +################################################## +dev: # Run the Next.js local dev server in Docker + docker compose up --detach nextjs + docker compose logs --follow nextjs + +storybook: # Run the Storybook local dev server in Docker + docker compose up --detach storybook + docker compose logs --follow storybook + +stop: + docker-compose down \ No newline at end of file diff --git a/app/README.md b/app/README.md index 58d9c8e3..8a6d653f 100644 --- a/app/README.md +++ b/app/README.md @@ -11,13 +11,13 @@ ├── .storybook # Storybook configuration ├── public # Static assets │ └── locales # Internationalized content -├── src # JS source code +├── src # Source code │ ├── components # Reusable UI components -│ └── pages # Page routes and data fetching -│   ├── api # API routes (optional) -│   └── _app.tsx # Global entry point +│ ├── pages # Page routes and data fetching +│   │ ├── api # API routes (optional) +│   │ └── _app.tsx # Global entry point +│ └── styles # Sass & design system settings ├── stories # Storybook pages -├── styles # Sass & design system settings └── tests ``` @@ -31,6 +31,10 @@ Files in the `pages/api` are treated as [API routes](https://nextjs.org/docs/api ### Getting started +The application can be ran natively or in a Docker container. + +#### Native + From the `app/` directory: 1. Install dependencies @@ -47,14 +51,26 @@ From the `app/` directory: ``` 1. Navigate to [localhost:3000](http://localhost:3000) to view the application -Alternatively, you can run the application in a Docker container: +##### Other scripts + +- `npm run build` - Builds the production Next.js bundle +- `npm start` - Runs the Next.js server, after building the production bundle + +#### Docker + +Alternatively, you can run the application in a Docker container. -1. From the root directory run `docker compose up -d --build` +From the `app/` directory: -Other scripts: +1. Run the local development server + ```bash + make dev + ``` +1. Navigate to [localhost:3000](http://localhost:3000) to view the application -- `npm run build` - Builds the production bundle -- `npm start` - Runs the app in production mode +##### Other scripts + +- `make release-build` - Creates the Docker image for deployment to the cloud ## 🖼️ Storybook @@ -62,18 +78,27 @@ Storybook is a [frontend workshop](https://bradfrost.com/blog/post/a-frontend-wo See the [Storybook Next.js documentation](https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs) for more information about using Storybook with Next.js +Similar to the Next.js application, Storybook can be ran natively or in a Docker container. + +#### Native + From the `app/` directory: 1. `npm run storybook` 2. Navigate to [localhost:6006](http://localhost:6006) to view -Alternatively, you can run Storybook in a Docker container: +##### Other scripts -1. From the root directory run `docker compose exec nextjs npm run storybook` +- `npm run storybook-build` - Exports a static site to `storybook-static/` -Other scripts: +#### Docker -- `npm run storybook-build` - Exports a static site to `storybook-static/` +Alternatively, you can run Storybook in a Docker container. + +From the `app/` directory: + +1. `make storybook` +2. Navigate to [localhost:6006](http://localhost:6006) to view ## 🐛 Testing diff --git a/app/next.config.js b/app/next.config.js index 1cbbb540..da9c87dd 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -17,6 +17,9 @@ const nextConfig = { basePath, i18n, reactStrictMode: true, + // Output only the necessary files for a deployment, excluding irrelevant node_modules + // https://nextjs.org/docs/app/api-reference/next-config-js/output + output: "standalone", sassOptions: appSassOptions, transpilePackages: [ // Continue to support older browsers (ES5) diff --git a/app/src/pages/_app.tsx b/app/src/pages/_app.tsx index a2b8a631..67a974eb 100644 --- a/app/src/pages/_app.tsx +++ b/app/src/pages/_app.tsx @@ -2,10 +2,10 @@ import { appWithTranslation } from "next-i18next"; import type { AppProps } from "next/app"; import Head from "next/head"; -import "../../styles/styles.scss"; - import Layout from "../components/Layout"; +import "../styles/styles.scss"; + function MyApp({ Component, pageProps }: AppProps) { return ( <> diff --git a/app/styles/_uswds-theme-custom-styles.scss b/app/src/styles/_uswds-theme-custom-styles.scss similarity index 100% rename from app/styles/_uswds-theme-custom-styles.scss rename to app/src/styles/_uswds-theme-custom-styles.scss diff --git a/app/styles/_uswds-theme.scss b/app/src/styles/_uswds-theme.scss similarity index 100% rename from app/styles/_uswds-theme.scss rename to app/src/styles/_uswds-theme.scss diff --git a/app/styles/styles.scss b/app/src/styles/styles.scss similarity index 100% rename from app/styles/styles.scss rename to app/src/styles/styles.scss diff --git a/docker-compose.yml b/docker-compose.yml index 58bee2ef..b7800bb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,33 @@ -# Specify current major version of docker compose. -# See https://docs.docker.com/compose/compose-file/compose-versioning/ -version: '3' - +# Local development compose file services: - # Define a service for the Next.js application. nextjs: - build: ./app - # To support local development, runs the container in development mode. - # This allows Next.js to do things like hot reload. - command: ["npm", "run", "dev"] + container_name: next-dev + build: + context: ./app + target: dev + env_file: + # Add your non-secret environment variables to this file: + - ./app/.env.development + # If you have secrets, add them to this file and uncomment this line: + # - ./app/.env.local + volumes: + - ./app/src:/app/src + - ./app/public:/app/public + restart: always ports: - # Expose a port to access the application. - 3000:3000 - # Expose a port for storybook. - - 6006:6006 - volumes: - # Mount the app directory for faster local development. - - ./app:/srv - # Use a named volume for the node_modules so that the container uses - # the guest machine's node_modules dir instead of the host machine's node_modules directory, which might be divergent. - # This also means that if you run `npm install ` on the host machine in development - # (which will update `package-lock.lock`), you'll also need to run `docker compose exec nextjs npm ci` to update - # `node_modules` in the guest machine. - - nextjs_nodemodules:/srv/node_modules - -volumes: - nextjs_nodemodules: + storybook: + container_name: storybook + build: + context: ./app + target: dev + command: npm run storybook + volumes: + - ./app/src:/app/src + - ./app/public:/app/public + - ./app/.storybook:/app/.storybook + - ./app/stories:/app/stories + restart: always + ports: + - 6006:6006