Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.git
.github
.codesandbox

**/node_modules
**/.cache
**/.turbo
**/.next
**/dist
**/cjs
**/*.tsbuildinfo

website/public
storybook/storybook-static
api/dist

*.log
*.swp
*.swo
*~

42 changes: 42 additions & 0 deletions ACCEPTANCE_TEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Acceptance Test Plan (Docker Compose)

This plan verifies a containerized Nivo website build with a co-located Storybook at `/storybook/`.

## Preconditions

- Docker is running.
- You are in the repo root.

## A. Website + Storybook (No Render API)

Bring up the website-only stack:

```bash
docker compose -f deploy/compose.website.yml up -d --build
```

Smoke checks:

```bash
curl -sS -I http://localhost:8080/ | tr -d '\r'
curl -sS -I http://localhost:8080/storybook/ | tr -d '\r'
curl -sS -I http://localhost:8080/storybook/sb-manager/runtime.js | tr -d '\r'
```

Expect:

- `/` returns `200` and `Cache-Control: public, max-age=1800`
- `/storybook/` returns `200`
- Storybook hashed assets return `Cache-Control: public, max-age=31536000, immutable`

Bring it down:

```bash
docker compose -f deploy/compose.website.yml down --remove-orphans
```

## Optional: Render API

The website includes `/<chart>/api/` pages that POST chart props to a render API.

This repo historically used `https://nivo-api.herokuapp.com/nivo` for that, but if you want those pages to work without relying on an external service, the render API needs to be self-hosted and proxied under the same origin (e.g. at `/nivo/`).
22 changes: 22 additions & 0 deletions DEPLOY_DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Docker Deployment (Website + Storybook, Optional Render API)

This repo includes Dockerfiles and docker-compose examples to build and serve:

- the Nivo website (Gatsby static build)
- Storybook at `/storybook/`

## Website + Storybook

```bash
docker compose -f deploy/compose.website.yml up -d --build
```

Open:

- Website: `http://localhost:8080/`
- Storybook: `http://localhost:8080/storybook/`

## Notes

- Gatsby inlines `GATSBY_*` variables at build time.
- `SITE_URL` is also a build-time value used by Gatsby.
69 changes: 69 additions & 0 deletions Dockerfile.nivo-website
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# syntax=docker/dockerfile:1.7
#
# Builds the Gatsby site in ./website (monorepo workspace) and serves it via nginx.
#
# Build-time:
# docker build -f Dockerfile.nivo-website --build-arg SITE_URL=http://localhost:8080 -t nivo-website:dev .
#
# Run:
# docker run --rm -p 8080:80 nivo-website:dev
#
FROM node:22-bookworm-slim AS builder

WORKDIR /app

# Avoid downloading large E2E/browser binaries during image build.
ENV CYPRESS_INSTALL_BINARY=0
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
ENV PUPPETEER_SKIP_DOWNLOAD=1
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1

# Use the repo's pinned pnpm version via corepack.
ENV PNPM_HOME=/pnpm
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare pnpm@10.11.0 --activate

# Copy only what's needed to build the website.
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY tsconfig*.json babel.config.js lerna.json eslint.config.mjs ./
COPY Makefile ./Makefile
COPY conf ./conf
COPY scripts ./scripts
COPY packages ./packages
COPY storybook ./storybook
COPY website ./website

RUN pnpm install --frozen-lockfile

# The website depends on workspace packages, so build them first.
RUN pnpm run pkgs:types:check && pnpm run pkgs:build

# Gatsby uses SITE_URL during build (see website/gatsby-config.ts).
ARG SITE_URL=http://localhost:8080
ENV SITE_URL="$SITE_URL"

# Client-side API base URL for the "HTTP API" demo pages (Gatsby inlines GATSBY_* at build time).
# Keep WITHOUT a trailing slash.
# Default to the historical render API (override to use your own).
ARG GATSBY_NIVO_API_URL=https://nivo-api.herokuapp.com/nivo
ENV GATSBY_NIVO_API_URL="$GATSBY_NIVO_API_URL"

# Client-side Storybook base URL (also inlined by Gatsby).
# Keep WITHOUT a trailing slash unless you're using a path-only URL like /storybook/.
ARG GATSBY_STORYBOOK_URL=/storybook/
ENV GATSBY_STORYBOOK_URL="$GATSBY_STORYBOOK_URL"

RUN pnpm -C website build
RUN pnpm --filter storybook build


FROM nginx:1.27-alpine AS runtime

COPY --from=builder /app/website/public /usr/share/nginx/html
COPY --from=builder /app/storybook/storybook-static /usr/share/nginx/html/storybook

# Default to the website-only nginx config; use NGINX_CONF to include an API proxy.
ARG NGINX_CONF=deploy/nginx.website.conf
COPY ${NGINX_CONF} /etc/nginx/conf.d/default.conf

EXPOSE 80
11 changes: 11 additions & 0 deletions deploy/compose.website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
website:
build:
context: ..
dockerfile: Dockerfile.nivo-website
args:
# Serve website+storybook only (no /nivo proxy).
NGINX_CONF: deploy/nginx.website.conf
restart: unless-stopped
ports:
- "8080:80"
63 changes: 63 additions & 0 deletions deploy/nginx.website.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

# Compression helps a lot for Gatsby's JS/CSS bundles.
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types
text/plain
text/css
application/javascript
application/json
image/svg+xml;

# These should update quickly when you redeploy.
location = /sw.js {
add_header Cache-Control "no-cache" always;
try_files $uri =404;
}

location = /manifest.webmanifest {
add_header Cache-Control "no-cache" always;
try_files $uri =404;
}

# Self-hosted Storybook (built from ./storybook) served at /storybook/.
# Storybook assets will be matched by the global hashed-asset cache rule below.
location /storybook/ {
add_header Cache-Control "public, max-age=1800" always;
try_files $uri $uri/ /storybook/index.html =404;
}

# Serve pre-built Gatsby pages as-is.
location / {
# Cache HTML for 30 minutes to reduce origin load.
# Tradeoff: users may see stale HTML briefly after a deploy.
add_header Cache-Control "public, max-age=1800" always;
try_files $uri $uri/ =404;
}

# Root-level and /static assets are content-hashed in their filenames: cache aggressively.
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp|avif|woff2?|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}

# Gatsby page-data isn't content-hashed; cache briefly to reduce chatter, but allow quick updates.
location ^~ /page-data/ {
add_header Cache-Control "public, max-age=300" always;
try_files $uri =404;
}

# Basic hardening headers (safe for static content).
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

3 changes: 2 additions & 1 deletion website/src/components/nav/FullNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import media from '../../theming/mediaQueries'
import * as nav from '../../data/nav'
import { FullNavComponentLink } from './FullNavComponentLink'
import config from '../../data/config'

export const FullNav = memo(() => {
return (
Expand All @@ -37,7 +38,7 @@ export const FullNav = memo(() => {
<InternalLink to="/references/">References</InternalLink>
<InternalLink to="/faq/">FAQ</InternalLink>
<ExternalLink
href="https://nivo.rocks/storybook/"
href={config.storybookUrl}
target="_blank"
rel="noopener noreferrer"
>
Expand Down
3 changes: 2 additions & 1 deletion website/src/components/nav/HeaderNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import media from '../../theming/mediaQueries'
import ThemeSelector from '../ThemeSelector'
import * as nav from '../../data/nav'
import { NavToggleButton } from './NavToggleButton'
import config from '../../data/config'

interface HeaderNavProps {
isNavOpen: boolean
Expand Down Expand Up @@ -35,7 +36,7 @@ export const HeaderNav = ({ isNavOpen, toggleNav }: HeaderNavProps) => {
</HeaderSub>
</HeaderItem>
<HeaderExternalLink
href="https://nivo.rocks/storybook/"
href={config.storybookUrl}
target="_blank"
rel="noopener noreferrer"
>
Expand Down
8 changes: 5 additions & 3 deletions website/src/data/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ export default {
// nivoApiUrl: 'http://localhost:3030/nivo',
// storybookUrl: 'http://localhost:6006/',

// production
nivoApiUrl: 'https://nivo-api.herokuapp.com/nivo',
storybookUrl: 'https://nivo.rocks/storybook/',
// production (override at build time via Gatsby env vars)
// Note: keep these base URLs WITHOUT a trailing slash.
nivoApiUrl: process.env.GATSBY_NIVO_API_URL || 'https://nivo-api.herokuapp.com/nivo',
// Default to the self-hosted storybook served from this same origin.
storybookUrl: process.env.GATSBY_STORYBOOK_URL || '/storybook/',
}
Loading