This repo contains the code responsible for building the Apollo Docs site. It also houses the content for the Apollo Basics and GraphOS docsets.
We use the word docset to describe an individual docs website. For example, "I just updated the Android docset".
The central piece of this repo, the docs infrastructure, is a Gatsby website that sources MDX and Markdown files from the remote git repos of Apollo's tools. It pulls in all of this data and outputs a single static site. To learn more about this approach and why we built this, check out this section.
- Developing locally
- Faster startup
- Meet the docsets
- Docset configuration
- Publish and preview
- Authoring
- History
- Notable changes to authoring patterns
Volta ensures we're all using the same Node and NPM versions when running the site. Either install Volta before proceeding, or install direnv to install Volta automatically when you cd
into your local docs
directory.
-
Install NPM dependencies:
npm i
If it's your first time running the docs site locally, you must link your local directory with Netlify. To do this, you must be logged in to the
netlify
CLI with an account that has access to our organization in Netlify. Apollo Staff can log into the account using the Netlify Single Sign-On (SSO) option and specifying theapollo-main
identifier during the login process.npx netlify login
-
Link your local environment with its corresponding Netlify site. Follow the prompts that appear in your terminal and link the site based on its git repository:
# If netlify-cli is installed globally or as a dependency in the remote docset repo: npx netlify link # Otherwise: npx -p netlify-cli netlify link
-
Start the local development environment:
npm start
⌚ The first run may take a long time as it has to source a lot of content, but subsequent runs will be shorter since most of that data will have been cached.
By default, running the local development environment will build a site with everything included. If you're working on a content edit to a single docset and want to preview those changes within the context of the docs site, you can specify the path to another docs repo that you have checked out on your computer. This will cause the site to source content only from the provided local directory, and changes to content in those repos will be hot-reloaded. 🔥
# this will build the React at the root of your local development environment
npm start -- ../apollo-client
For your convenience, this repo also comes with special start
NPM scripts for each docset, assuming you have this repo checked out in the same directory as the other OSS repos.
npm run start:client
npm run start:server
npm run start:ios
npm run start:kotlin
npm run start:federation
npm run start:rover
npm run start:router
Check out the package.json
to see how these scripts work!
The GraphOS and Apollo basics docs content lives in this repo. To spin up a development environment serving only these docsets, there's the start:local
script. This will result in much faster startup time since we're not sourcing any remote data. Use this script if you're working on changes in either of those docsets or website UI changes.
npm run start:local
The docs content is written and maintained in the following places. Many of them are other git repositories, but some of the content on this site is stored in this repo.
- Apollo Client (React)
- Apollo Server
- Apollo iOS
- Apollo Kotlin
- Apollo Federation
- Rover CLI
- Apollo Router
A docset is made up of mostly Markdown/MDX files and a config.json
file that configures its settings.
The config.json
file lives at the root of its docset's content directory, and it allows authors to configure the docset's title, version name, and sidebar nav. Here's an example of one:
{
"title": "Apollo Server",
"version": "v3",
"versionBanner": {
"link": {
"to": "/v3-deprecation-page",
"content": "Check out the deprecation page",
},
"message": "Apollo Server v3 is no longer maintained."
},
"algoliaFilters": ["docset:server", ["docset:react", "docset:federation"]],
"sidebar": {
"Introduction": "/",
"Get started": "/getting-started",
"New in v3": {
"Migrating to Apollo Server 3": "/migration",
"Changelog": "https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md"
}
}
}
Name | Required? | Description |
---|---|---|
title | yes | The title of the docset. It is used to construct page titles and shown in the header when the docset is selected. |
sidebar | yes | A JSON object mapping sidebar nav labels to their paths. Use paths beginning with a slash, relative to the root of the content directory for internal links. Full URLs are transformed into external links that open in a new tab. These objects can be nested to define categories and subcategories in the nav. |
version | no | A string representing the version of the software that is being documented, i.e. "v3". This value is shown in the version dropdown if multiple versions of a docset are configured. |
algoliaFilters | no | An array of filters that affect the ranking of search results when a search is made within a particular docset. This is passed to Algolia as an optionalFilters parameter, which you can learn more about here. |
internal | no | Set to true if you want your docset to be internal-only. |
versionBanner | no | A JSON object used to customize the VersionBanner link url and text. |
To create a local docset, add a new directory to the src/content
directory of this repo, and drop in an index.md
file and a config.json
file. Next, head over to the sources/local.yml
file and add a line mapping the URL path that you want the docset to live at to the location of its content.
graphos: src/content/graphos
Restart your local development environment and the new docset will be available to peruse and develop. 🚀
Remote docsets live within the docs/source
directory of the public repo for the library that is being documented. The docs/source
directory must contain some Markdown/MDX files and a config.json
file.
Configured repos must be public so that we can pull down their files at build time without permission issues
To add a remote docset to the website, add a record in the sources/remote.js
file. This record should map the URL path you want the docset to live at to the repo URL, called remote
, and the name of the branch
that content should be sourced from.
// sources/remote.js
module.exports = {
// ...other sources
react: {
remote: "https://github.com/apollographql/apollo-client",
branch: "main",
},
};
This website presents multiple versions of docs for the same subject as options within a version dropdown in the main nav. It automatically treats multiple docsets with the same git remote URL as different versions of the same docs. So to add a new version, add a second entry to the sources/remote.js
file with your desired path (appending "/v2" in this case) and updated branch name.
// sources/remote.js
module.exports = {
// ...other sources
react: {
remote: "https://github.com/apollographql/apollo-client",
branch: "main",
},
"react/v2": {
remote: "https://github.com/apollographql/apollo-client",
branch: "version-2.6",
},
};
Next, these two docsets must specify the label that they want to appear for that version in the version dropdown. This is done by adding a version
field to each version's config.json
.
{
"title": "Apollo Client",
"version": "v2",
"sidebar": {...}
}
You can publish docsets that are viewable only by Apollo team members by setting the internal
option to true
in your config.json file.
{
"internal": true
}
If a visitor to that page is logged in to Apollo Studio and is a member of one of our internal orgs, the page content will be rendered normally. If neither of those conditions are true, a 404 page will be shown. Internal-only pages are excluded from the sitemap and won't be indexed by Google.
It's important to note that you must sign in to and out of your account using Studio or Odyssey, as the docs don't currently have their own sign in form. For local development, sign in to the staging Studio.
If you would like an icon with a tooltip for any use case across the sidebar navigation items (preview tag, experimental tag, etc.), you can use an array for the path, where the first item is the URL path, and the second item is an array of tags.
{
"Performance alerts": [
"/notifications/performance-alerts", // nav item path
["experimental"] // array of tags
],
}
We currently support tags for enterprise
, preview
& experimental
.
By default, all sidebar categories are shown as open on initial page load. You can affect the default open/closed state of any sidebar category by placing your category config within a tuple, similar to the technique above. The first element is the category configuration object, and the second element is a boolean. If the second element is true
, that category will be collapsed by default.
{
"Release Policies": [
{
"Product launch stages": "/resources/product-launch-stages",
"Elastic License v2 FAQ": "/resources/elastic-license-v2-faq"
},
true
]
}
Redirects can continue to be written in the _redirects
file in the docs/source
directory of each docset. These redirects can be written in the Netlify redirects syntax, but come with one catch. The from
path must be relative to the root of the particular docset that the redirect pertains to, but the to
path must be relative to the root of the apollographql.com
domain. That means that the path you want to redirect the user to must include the /docs
path prefix to redirect to a page in the docs.
# in the React docs _redirects file
/v2.4 /docs/react/v2/
All of the redirects from each docset will be bundled together at build time, and deployed as one combined _redirects
file deployed with the built docs site.
This website gets rebuilt and deployed to Netlify every time something is committed to its default branch. Deploy previews are automatically created for new PRs.
"But what about changes to remote docsets?", I hear you say. Netlify doesn't let us configure a site to listen for changes in more than one repo.
To work around this, we use GitHub Actions to trigger a new production deploy every time docs-related changes are made. To enable this behavior, add a publish workflow in your repo.
- Go to the "Actions" tab in GitHub. If you already have workflows, click the "New workflow" button to get to the "Get started with GitHub Actions" page.
- Scroll down to the "By Apollo GraphQL" section.
- Find the "Docs Production Deploy Workflow" action and click the "Configure" button.
This will bring up a page where you can edit the workflow template. If your docset manages more than one version, add the version branches to the branches
block in the workflow. Below is an example of a docs-publish.yml
workflow:
name: Deploy docs to production
on:
push:
branches:
- main
- version-2 # or whatever your version branch(es) are called
paths:
- docs/**
jobs:
publish:
uses: apollographql/docs/.github/workflows/publish.yml@main
secrets:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
This workflow references a shared workflow hosted in this repo, so any changes that affect the way the action works will likely take place in that file, and you won't need to make any changes to the workflow in your own repo.
You will also need to configure the remote docset repo's access to the required organization secrets before you run the action. To do this, visit the "Action secrets" settings page in the Apollo GraphQL organization and update the NETLIFY_AUTH_TOKEN
and NETLIFY_SITE_ID
secrets to include the repo in the "Selected repositories" settings for each secret.
We also use Netlify to build and publish deploy previews for PRs that include docs changes. To set up deploy previews in any repo, add a netlify.toml
file to the root of your repo, or update the one you have to look like this:
[build]
ignore = "exit 0"
[build.environment]
NODE_VERSION = "16"
[context.deploy-preview.build]
base = "docs"
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ."
command = """\
cd ../
rm -rf monodocs
git clone https://github.com/apollographql/docs --branch main --single-branch monodocs
cd monodocs
npm i
cp -r ../docs local
DOCS_LOCAL=true npm run build \
"""
publish = "../monodocs/public"
This configures Netlify to:
- Ignore production builds (these are handled by Zapier).
- Set the Node.js version to 16 (and NPM v8)
- Ignore deploy previews without any docs changes
- Run a sequence of commands to build a deploy preview based on content from the OSS repo
- Set the publish directory to the built site
If your docset manages multiple versions, please make sure you've configured your version branches as branch deploys in the Netlify UI. This will ensure that deploy previews get built for PRs based on either the default branch or a version branch.
Docs articles are authored in Markdown syntax. This allows us to express lists, tables, blockquotes, and other HTML elements in a simple format. The following are some conventions and techniques to keep in mind when writing articles.
Frontmatter is a block of YAML code surrounded by ---
at the top of a Markdown file. We use frontmatter to configure the page title and description of each article.
---
title: Proxy configuration
description: Configuring proxy settings for outgoing requests
---
We can configure which headings are shown in an individual page's table of contents by providing a headingDepth
frontmatter property. By default, headings up to a depth of 3 (h1, h2, and h3) are shown.
---
headingDepth: 4 # show headings up to h4
---
Links between docs articles should be written as relative paths. For example, if you wanted to link from the schema/custom-scalars
article in the Apollo Server docs to the getting-started
page at the root of the content directory, you would write:
[get started](../getting-started)
Writing links using absolute paths, i.e. /getting-started
will result in that link taking the user to /docs/getting-started
in production, instead of /docs/apollo-server/getting-started
as intended.
That being said, links between docsets can (and should) be made using absolute paths. Previously we had to link between other docsets using full URLs, i.e. https://www.apollographql.com/docs/apollo-server
. Now those links can be written as /apollo-server
and they'll benefit from the same snappy, instant, client-side routing behaviour that internal links get.
In addition to supporting syntax highlighting, our code blocks also allow you to specify the title of the file that is being represented, and highlight specific lines.
In the following example, a YAML code block is configured with a title
of "config.yml" and the numbers in the curly braces represent the lines that are meant to be highlighted—lines 1, 3, 4, and 5 in this case.
```yml title="config.yml" {1,3-5}
title: Apollo Client
version: v3
sidebar:
Introduction: /
Why Apollo Client: /why-apollo
```
MDX is just like Markdown except you can import and render React components within it. MDX files use the .mdx
extension, and you may be familiar with them if you've worked in any of our docsets before.
Gatsby does not currently support MDX v2 (MDX v2 only uses ESM, which Gatsby does not yet support), so make sure that the MDX syntax you are using when authoring in an
.mdx
file is compatible with MDX v1.
MDX is also composable; it allows you to render other MDX files within as if it were a React component. One pattern we use when sharing MDX content is saving the shared bits in a folder outside the docs/source
directory so that those files aren't rendered as pages in the docs site.
shared/configure-project.mdx
1. Sign up for an Apollo account
2. Create a graph in Apollo Studio
3. Add environment variables to your project
source/get-started.mdx
import ConfigureProject from "../shared/configure-project.mdx";
## Introduction
Here's how to configure a project:
<ConfigureProject />
You can import modules within MDX files, but we also provide a variety of components to every MDX file—no import required.
A Chakra UI Button
component that behaves as a link. These links follow the same rules as writing Markdown links. Refer to the Chakra docs for more props that can affect the style and behavior of the button.
<ButtonLink variant="outline" href="/apollo-server/v2">
View older version
</ButtonLink>
<ButtonLink colorScheme="indigo" href="./get-started">
Get started
</ButtonLink>
Show and hide a section of content with a configurable message. Wrap this component around a block of Markdown, and make sure to add empty lines between the opening and closing tags and the content within them.
<ExpansionPanel title="Show solution">
This content will be hidden by default, but can be expanded if the user clicks on the panel.
- Markdown
- works
- here
</ExpansionPanel>
Wrap TypeScript code blocks in a MultiCodeBlock
component to automatically transpile them into JavaScript. A languages will be rendered as tabs above the code block for the user to switch between the two options. This feature works on code blocks tagged with ts
or tsx
.
You can also manually add multiple code blocks with different languages to the MultiCodeBlock
to have them behave the same way.
<MultiCodeBlock>
```ts
const foo: number = 123;
```
</MultiCodeBlock>
Render multiple code blocks side-by-side. It takes a cols
prop that affects the number of columns rendered. By default, it renders two columns.
<CodeColumns cols={3}>
```js
const foo = 123;
```
```css
.link {
background-color: red;
}
```
```html
<a class="link" href="https://example.com">Click here</a>
```
</CodeColumns>
A YouTube player exported from MDX Embed. Check out all of the different props and options on their docs.
Check out this introduction to Apollo Studio:
<YouTube youTubeId="sarXMaz3OpY" />
This component is currently only used in the Apollo Client docset. It takes a property name and renders auto-generated documentation created by running TypeDoc in the Apollo Client repo.
## The `ApolloClient` constructor
<TypeScriptApiBox name="ApolloClient.constructor" />
Previous to this system, we built our docs site by building each repo's docs individually using a shared Gatsby theme. Each site would be deployed to Netlify and "stitched" together to make one continuous website using Netlify path rewrites like this:
/docs/ios/* https://apollo-ios-docs.netlify.app/:splat 200!
/docs/react/* https://apollo-client-docs.netlify.app/:splat 200!
All of our path rewriting happens in the website router repo.
The main benefit of this approach is that it let us colocate docs articles with the libraries that they're describing. The iOS docs live in the iOS client repo, and so on. This was an organization that everybody liked. 🤝
One drawback was that this would introduce extra configuration files and dependencies to each repo's docs
directory. This meant that Dependabot PRs would be opened on every repo when one of the docs site dependencies had a security vulnerability.
We use Renovate to automatically keep our dependencies up-to-date when new versions are published. However, problems arose when library maintainers (rightly) wanted to use a newer version of npm
but the docs were meant to be installed/built using an older version. The docs dependencies would be installed using the new tooling and build errors would crop up.
Another downside was the complicated flow for publishing changes to the docs infrastructure. Let's say we wanted to add a copy button to the code blocks. We would have to:
- Make changes to code in the Gatsby theme repo
- Publish a prerelease version of the theme to NPM
- Checkout a new branch on the docs repo you want to test on
- Install the prerelease version
- Verify changes
- Publish new version of the theme
- Wait for Renovate to pick up the change and post a PR (~1h) or make your own PR upgrading the theme in every. single. repo. 💀
This new infrastructure fixes these issues by centralizing the website code and dependencies in one place, and pulling in content from the library repos. Each library's docs
directory now only needs to include Markdown/MDX files and images—no Gatsby configuration or dependencies.
We also increase our pace of iteration on website changes and make it easier to issue bug fixes or address security vulnerabilities by cutting out the extra steps of publishing to NPM and installing new versions of a package on each docs repo. And by moving non-OSS-related docsets into the central docs repo, we're able to consolidate docs-only repos and make those docsets easier to update.
Using the old infrastructure, we were spending a lot of time building our docs. Because each library repo was tied to its own Netlify site, that site would redeploy any time any commit was pushed to its default branch. In other words, changes that didn't affect the docs were still triggering builds. This resulted in much more building than necessary.
Site | Builds | Average time | Total time |
---|---|---|---|
apollo-federation-docs | 424 | 4m 55s | 2,084m |
apollo-client-docs | 237 | 8m 21s | 1,979m |
apollo-router-docs | 437 | 2m 21s | 1,027m |
apollo-ios-docs | 213 | 3m 41s | 784m |
New docs infra ✨ | 94 | 7m 30s | 705m |
apollo-server-docs | 140 | 4m 34s | 638m |
apollo-android-docs | 111 | 4m 30s | 499m |
apollo-cli-docs | 87 | 2m 6s | 182m |
studio-docs | 44 | 3m 11s | 139m |
apollo-docs-index | 38 | 3m 4s | 116m |
Total | 1,825 | N/A | 8,153m |
During this period, the new docs took less time to build the entirety of our docs than the Apollo Client docs took to build just itself. It built 94 times, and each build was directly related to a change in the docs UI or content. We can't say the same for the other 1,731 combined builds that happened across other docsets.
I believe this represents an opportunity to cut down the amount of Netlify build time we spend on the docs, and as a result save us some cost in that area.
Mostly all of the way that content authoring works in our docs stays the same with this change, with a few exceptions:
Previously, we would import components like ExpansionPanel
and CodeColumns
from gatsby-theme-apollo-docs
in our MDX files. Now, all components are automatically available without importing.
- import {ExpansionPanel} from 'gatsby-theme-apollo-docs';
<ExpansionPanel>
Hidden content in here
</ExpansionPanel>
In addition, we used to use the Button
component from @apollo/space-kit
in some places. Now a Button
component is available in all MDX pages. Here's an example comparing the old way of using buttons to the new way:
- import {Button} from '@apollo/space-kit';
- import {colors} from 'gatsby-theme-apollo-core';
import {Link} from 'gatsby';
<Button
- color={colors.primary}
- as={<Link to="./get-started" />}
+ colorScheme="indigo"
+ as={Link}
+ to="./get-started"
>
Click me
</Button>
Line highlighting ({x-y}
) and title attributes used to be supplied in one continuous string attached to the language tag on a code block. Now they must be added with space between each property. The title must be wrapped in quotes, and the colon before title
can be removed.
- ```js{3-5}:title=index.js
+ ```js {3-5} title="index.js"
const num = 123;
```
More info about code blocks here.
Previously, links within a docset could be written as absolute paths, i.e. /get-started
. Now, all internal links should be written as relative paths, i.e. ./get-started
or ../get-started
, depending on where the destination page lives relative to the page it's being linked from.
More information about linking here.