diff --git a/website/README.md b/website/README.md
index a8fa9c175..1083665ee 100644
--- a/website/README.md
+++ b/website/README.md
@@ -78,6 +78,7 @@ bit of time.
 ```yaml
 ├── docs                 # the actual markdown content
 ├── meta                 # JSON-formatted site metadata
+|   ├── dynamicMeta.js   # At build time generated meta data
 |   ├── languages.json   # supported languages and statistical models
 |   ├── sidebars.json    # sidebar navigations for different sections
 |   ├── site.json        # general site metadata
diff --git a/website/docs/api/morphology.mdx b/website/docs/api/morphology.mdx
index 565e520b5..080f1c3fe 100644
--- a/website/docs/api/morphology.mdx
+++ b/website/docs/api/morphology.mdx
@@ -105,11 +105,11 @@ representation.
 
 ## Attributes {#attributes}
 
-| Name          | Description                                                                                                                  |
-| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------- |
-| `FEATURE_SEP` | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) feature separator. Default is `          | `. ~~str~~ |
-| `FIELD_SEP`   | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) field separator. Default is `=`. ~~str~~ |
-| `VALUE_SEP`   | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) value separator. Default is `,`. ~~str~~ |
+| Name          | Description                                                                                                                     |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| `FEATURE_SEP` | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) feature separator. Default is `\|`. ~~str~~ |
+| `FIELD_SEP`   | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) field separator. Default is `=`. ~~str~~    |
+| `VALUE_SEP`   | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) value separator. Default is `,`. ~~str~~    |
 
 ## MorphAnalysis {#morphanalysis tag="class" source="spacy/tokens/morphanalysis.pyx"}
 
diff --git a/website/docs/api/sentencizer.mdx b/website/docs/api/sentencizer.mdx
index f5017fbdb..2a1d6a119 100644
--- a/website/docs/api/sentencizer.mdx
+++ b/website/docs/api/sentencizer.mdx
@@ -38,8 +38,8 @@ how the component should be configured. You can override its settings via the
 > ```
 
 | Setting                                  | Description                                                                                                                                            |
-| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ |
-| `punct_chars`                            | Optional custom list of punctuation characters that mark sentence ends. See below for defaults if not set. Defaults to `None`. ~~Optional[List[str]]~~ | `None` |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `punct_chars`                            | Optional custom list of punctuation characters that mark sentence ends. See below for defaults if not set. Defaults to `None`. ~~Optional[List[str]]~~ |
 | `overwrite` <Tag variant="new">3.2</Tag> | Whether existing annotation is overwritten. Defaults to `False`. ~~bool~~                                                                              |
 | `scorer` <Tag variant="new">3.2</Tag>    | The scoring method. Defaults to [`Scorer.score_spans`](/api/scorer#score_spans) for the attribute `"sents"` ~~Optional[Callable]~~                     |
 
diff --git a/website/docs/api/token.mdx b/website/docs/api/token.mdx
index 89bd77447..1eef1f4cd 100644
--- a/website/docs/api/token.mdx
+++ b/website/docs/api/token.mdx
@@ -101,7 +101,7 @@ Check whether an extension has been registered on the `Token` class.
 | `name`      | Name of the extension to check. ~~str~~             |
 | **RETURNS** | Whether the extension has been registered. ~~bool~~ |
 
-## Token.remove_extension {#remove_extension tag="classmethod" new=""2.0.11""}
+## Token.remove_extension {#remove_extension tag="classmethod" new="2.0.11"}
 
 Remove a previously registered extension.
 
diff --git a/website/docs/styleguide.mdx b/website/docs/styleguide.mdx
index 78b2c1d75..1f0899082 100644
--- a/website/docs/styleguide.mdx
+++ b/website/docs/styleguide.mdx
@@ -65,16 +65,7 @@ import { Colors, Patterns } from 'widgets/styleguide'
 
 ## Typography {#typography}
 
-import {
-  H1,
-  H2,
-  H3,
-  H4,
-  H5,
-  Label,
-  InlineList,
-  Comment,
-} from 'components/typography'
+import { H1, H2, H3, H4, H5, Label, InlineList } from 'components/typography'
 
 > #### Markdown
 >
diff --git a/website/gatsby-config.js b/website/gatsby-config.js
index 1d919dc33..ca3c75aad 100644
--- a/website/gatsby-config.js
+++ b/website/gatsby-config.js
@@ -13,16 +13,11 @@ const codeBlocksPlugin = require('./src/plugins/remark-code-blocks.js')
 
 // Import metadata
 const site = require('./meta/site.json')
-const sidebars = require('./meta/sidebars.json')
-const models = require('./meta/languages.json')
-const universe = require('./meta/universe.json')
+const { domain, nightly: isNightly, legacy: isLegacy } = site
+const { siteUrl } = require('./meta/dynamicMeta')
 
 const DEFAULT_TEMPLATE = path.resolve('./src/templates/index.js')
 
-const domain = process.env.BRANCH || site.domain
-const siteUrl = `https://${domain}`
-const isNightly = site.nightlyBranches.includes(domain)
-const isLegacy = site.legacy || !!+process.env.SPACY_LEGACY
 const favicon = `src/images/icon${isNightly ? '_nightly' : isLegacy ? '_legacy' : ''}.png`
 const branch = isNightly ? 'develop' : 'master'
 
@@ -34,30 +29,11 @@ const replacements = {
     SPACY_PKG_FLAGS: isNightly ? ' --pre' : '',
 }
 
-/**
- * Compute the overall total counts of models and languages
- */
-function getCounts(langs = []) {
-    return {
-        langs: langs.length,
-        modelLangs: langs.filter(({ models }) => models && !!models.length).length,
-        models: langs.map(({ models }) => (models ? models.length : 0)).reduce((a, b) => a + b, 0),
-    }
-}
-
 module.exports = {
     siteMetadata: {
         ...site,
-        sidebars,
-        ...models,
-        counts: getCounts(models.languages),
-        universe,
-        nightly: isNightly,
-        legacy: isLegacy,
-        binderBranch: domain,
         siteUrl,
     },
-
     plugins: [
         {
             resolve: `gatsby-plugin-sass`,
diff --git a/website/gatsby-node.js b/website/gatsby-node.js
index 4a580eb03..caa507db6 100644
--- a/website/gatsby-node.js
+++ b/website/gatsby-node.js
@@ -4,6 +4,9 @@ const { createFilePath } = require('gatsby-source-filesystem')
 const DEFAULT_TEMPLATE = path.resolve('./src/templates/index.js')
 const BASE_PATH = 'docs'
 const PAGE_EXTENSIONS = ['.md', '.mdx']
+const siteMetadata = require('./meta/site.json')
+const universe = require('./meta/universe.json')
+const models = require('./meta/languages.json')
 
 function replacePath(pagePath) {
     return pagePath === `/` ? pagePath : pagePath.replace(/\/$/, ``)
@@ -26,37 +29,6 @@ exports.createPages = ({ graphql, actions }) => {
             graphql(
                 `
                     {
-                        site {
-                            siteMetadata {
-                                sections {
-                                    id
-                                    title
-                                    theme
-                                }
-                                languages {
-                                    code
-                                    name
-                                    models
-                                    example
-                                    has_examples
-                                }
-                                universe {
-                                    resources {
-                                        id
-                                        title
-                                        slogan
-                                    }
-                                    categories {
-                                        label
-                                        items {
-                                            id
-                                            title
-                                            description
-                                        }
-                                    }
-                                }
-                            }
-                        }
                         allFile(filter: { ext: { in: [".md", ".mdx"] } }) {
                             edges {
                                 node {
@@ -107,8 +79,10 @@ exports.createPages = ({ graphql, actions }) => {
                     reject(result.errors)
                 }
 
-                const sectionData = result.data.site.siteMetadata.sections
-                const sections = Object.assign({}, ...sectionData.map((s) => ({ [s.id]: s })))
+                const sections = Object.assign(
+                    {},
+                    ...siteMetadata.sections.map((s) => ({ [s.id]: s }))
+                )
 
                 /* Regular pages */
 
@@ -183,8 +157,8 @@ exports.createPages = ({ graphql, actions }) => {
                     },
                 })
 
-                const universe = result.data.site.siteMetadata.universe.resources
-                universe.forEach((page) => {
+                const universeResources = universe.resources
+                universeResources.forEach((page) => {
                     const slug = `/universe/project/${page.id}`
 
                     createPage({
@@ -202,7 +176,7 @@ exports.createPages = ({ graphql, actions }) => {
                     })
                 })
 
-                const universeCategories = result.data.site.siteMetadata.universe.categories
+                const universeCategories = universe.categories
                 const categories = [].concat.apply(
                     [],
                     universeCategories.map((cat) => cat.items)
@@ -227,7 +201,7 @@ exports.createPages = ({ graphql, actions }) => {
 
                 /* Models */
 
-                const langs = result.data.site.siteMetadata.languages
+                const langs = models.languages
                 const modelLangs = langs.filter(({ models }) => models && models.length)
                 modelLangs.forEach(({ code, name, models, example, has_examples }, i) => {
                     const slug = `/models/${code}`
diff --git a/website/meta/dynamicMeta.js b/website/meta/dynamicMeta.js
new file mode 100644
index 000000000..da3031c13
--- /dev/null
+++ b/website/meta/dynamicMeta.js
@@ -0,0 +1,11 @@
+const site = require('./site.json')
+
+const domain = process.env.BRANCH || site.domain
+
+module.exports = {
+    domain,
+    siteUrl: `https://${domain}`,
+    nightly: site.nightlyBranches.includes(domain),
+    legacy: site.legacy || !!+process.env.SPACY_LEGACY,
+    binderBranch: domain,
+}
diff --git a/website/meta/site.json b/website/meta/site.json
index fa79d3c69..5dcb89443 100644
--- a/website/meta/site.json
+++ b/website/meta/site.json
@@ -27,7 +27,6 @@
         "indexName": "spacy"
     },
     "binderUrl": "explosion/spacy-io-binder",
-    "binderBranch": "spacy.io",
     "binderVersion": "3.4",
     "sections": [
         { "id": "usage", "title": "Usage Documentation", "theme": "blue" },
diff --git a/website/runtime.txt b/website/runtime.txt
index 475ba515c..cc1923a40 100644
--- a/website/runtime.txt
+++ b/website/runtime.txt
@@ -1 +1 @@
-3.7
+3.8
diff --git a/website/src/components/accordion.js b/website/src/components/accordion.js
index 00596326f..504f415a5 100644
--- a/website/src/components/accordion.js
+++ b/website/src/components/accordion.js
@@ -17,7 +17,7 @@ export default function Accordion({ title, id, expanded = false, spaced = false,
         [classes.hidden]: isExpanded,
     })
     // Make sure accordion is expanded if JS is disabled
-    useEffect(() => setIsExpanded(expanded), [])
+    useEffect(() => setIsExpanded(expanded), [expanded])
     return (
         <section className="accordion" id={id}>
             <div className={rootClassNames}>
diff --git a/website/src/components/code.js b/website/src/components/code.js
index c4a3640f6..28e3cb5d4 100644
--- a/website/src/components/code.js
+++ b/website/src/components/code.js
@@ -4,7 +4,6 @@ import classNames from 'classnames'
 import highlightCode from 'gatsby-remark-prismjs/highlight-code.js'
 import 'prismjs-bibtex'
 import rangeParser from 'parse-numeric-range'
-import { StaticQuery, graphql } from 'gatsby'
 import { window } from 'browser-monads'
 
 import CUSTOM_TYPES from '../../meta/type-annotations.json'
@@ -12,16 +11,20 @@ import { isString, htmlToReact } from './util'
 import Link, { OptionalLink } from './link'
 import GitHubCode from './github'
 import classes from '../styles/code.module.sass'
+import siteMetadata from '../../meta/site.json'
+import { binderBranch } from '../../meta/dynamicMeta'
 
 const WRAP_THRESHOLD = 30
 const CLI_GROUPS = ['init', 'debug', 'project', 'ray', 'huggingface-hub']
 
-export default (props) => (
+const CodeBlock = (props) => (
     <Pre>
         <Code {...props} />
     </Pre>
 )
 
+export default CodeBlock
+
 export const Pre = (props) => {
     return <pre className={classes.pre}>{props.children}</pre>
 }
@@ -172,7 +175,7 @@ function formatCode(html, lang, prompt) {
                     .split(' | ')
                     .map((l, i) => convertLine(l, i))
                     .map((l, j) => (
-                        <Fragment>
+                        <Fragment key={j}>
                             {j !== 0 && <span> | </span>}
                             {l}
                         </Fragment>
@@ -268,51 +271,34 @@ export class Code extends React.Component {
     }
 }
 
-const JuniperWrapper = ({ Juniper, title, lang, children }) => (
-    <StaticQuery
-        query={query}
-        render={(data) => {
-            const { binderUrl, binderBranch, binderVersion } = data.site.siteMetadata
-            const juniperTitle = title || 'Editable Code'
-            return (
-                <div className={classes.juniperWrapper}>
-                    <h4 className={classes.juniperTitle}>
-                        {juniperTitle}
-                        <span className={classes.juniperMeta}>
-                            spaCy v{binderVersion} &middot; Python 3 &middot; via{' '}
-                            <Link to="https://mybinder.org/" hidden>
-                                Binder
-                            </Link>
-                        </span>
-                    </h4>
+const JuniperWrapper = ({ Juniper, title, lang, children }) => {
+    const { binderUrl, binderVersion } = siteMetadata
+    const juniperTitle = title || 'Editable Code'
+    return (
+        <div className={classes.juniperWrapper}>
+            <h4 className={classes.juniperTitle}>
+                {juniperTitle}
+                <span className={classes.juniperMeta}>
+                    spaCy v{binderVersion} &middot; Python 3 &middot; via{' '}
+                    <Link to="https://mybinder.org/" hidden>
+                        Binder
+                    </Link>
+                </span>
+            </h4>
 
-                    <Juniper
-                        repo={binderUrl}
-                        branch={binderBranch}
-                        lang={lang}
-                        classNames={{
-                            cell: classes.juniperCell,
-                            input: classes.juniperInput,
-                            button: classes.juniperButton,
-                            output: classes.juniperOutput,
-                        }}
-                    >
-                        {children}
-                    </Juniper>
-                </div>
-            )
-        }}
-    />
-)
-
-const query = graphql`
-    query JuniperQuery {
-        site {
-            siteMetadata {
-                binderUrl
-                binderBranch
-                binderVersion
-            }
-        }
-    }
-`
+            <Juniper
+                repo={binderUrl}
+                branch={binderBranch}
+                lang={lang}
+                classNames={{
+                    cell: classes.juniperCell,
+                    input: classes.juniperInput,
+                    button: classes.juniperButton,
+                    output: classes.juniperOutput,
+                }}
+            >
+                {children}
+            </Juniper>
+        </div>
+    )
+}
diff --git a/website/src/components/footer.js b/website/src/components/footer.js
index df19443f2..759db8a7b 100644
--- a/website/src/components/footer.js
+++ b/website/src/components/footer.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { StaticQuery, graphql } from 'gatsby'
 import classNames from 'classnames'
 
 import Link from './link'
@@ -8,89 +7,55 @@ import Grid from './grid'
 import Newsletter from './newsletter'
 import ExplosionLogo from '-!svg-react-loader!../images/explosion.svg'
 import classes from '../styles/footer.module.sass'
+import siteMetadata from '../../meta/site.json'
 
 export default function Footer({ wide = false }) {
+    const { companyUrl, company, footer, newsletter } = siteMetadata
     return (
-        <StaticQuery
-            query={query}
-            render={(data) => {
-                const { companyUrl, company, footer, newsletter } = data.site.siteMetadata
-                return (
-                    <footer className={classes.root}>
-                        <Grid cols={wide ? 4 : 3} narrow className={classes.content}>
-                            {footer.map(({ label, items }, i) => (
-                                <section key={i}>
-                                    <ul className={classes.column}>
-                                        <li className={classes.label}>{label}</li>
-                                        {items.map(({ text, url }, j) => (
-                                            <li key={j}>
-                                                <Link to={url} hidden>
-                                                    {text}
-                                                </Link>
-                                            </li>
-                                        ))}
-                                    </ul>
-                                </section>
+        <footer className={classes.root}>
+            <Grid cols={wide ? 4 : 3} narrow className={classes.content}>
+                {footer.map(({ label, items }, i) => (
+                    <section key={i}>
+                        <ul className={classes.column}>
+                            <li className={classes.label}>{label}</li>
+                            {items.map(({ text, url }, j) => (
+                                <li key={j}>
+                                    <Link to={url} hidden>
+                                        {text}
+                                    </Link>
+                                </li>
                             ))}
-                            <section className={wide ? null : classes.full}>
-                                <ul className={classes.column}>
-                                    <li className={classes.label}>Stay in the loop!</li>
-                                    <li>Receive updates about new releases, tutorials and more.</li>
-                                    <li>
-                                        <Newsletter {...newsletter} />
-                                    </li>
-                                </ul>
-                            </section>
-                        </Grid>
-                        <div className={classNames(classes.content, classes.copy)}>
-                            <span>
-                                &copy; 2016-{new Date().getFullYear()}{' '}
-                                <Link to={companyUrl} hidden>
-                                    {company}
-                                </Link>
-                            </span>
-                            <Link
-                                to={companyUrl}
-                                aria-label={company}
-                                hidden
-                                className={classes.logo}
-                            >
-                                <ExplosionLogo width={45} height={45} />
-                            </Link>
-                            <Link to={`${companyUrl}/legal`} hidden>
-                                Legal / Imprint
-                            </Link>
-                        </div>
-                    </footer>
-                )
-            }}
-        />
+                        </ul>
+                    </section>
+                ))}
+                <section className={wide ? null : classes.full}>
+                    <ul className={classes.column}>
+                        <li className={classes.label}>Stay in the loop!</li>
+                        <li>Receive updates about new releases, tutorials and more.</li>
+                        <li>
+                            <Newsletter {...newsletter} />
+                        </li>
+                    </ul>
+                </section>
+            </Grid>
+            <div className={classNames(classes.content, classes.copy)}>
+                <span>
+                    &copy; 2016-{new Date().getFullYear()}{' '}
+                    <Link to={companyUrl} hidden>
+                        {company}
+                    </Link>
+                </span>
+                <Link to={companyUrl} aria-label={company} hidden className={classes.logo}>
+                    <ExplosionLogo width={45} height={45} />
+                </Link>
+                <Link to={`${companyUrl}/legal`} hidden>
+                    Legal / Imprint
+                </Link>
+            </div>
+        </footer>
     )
 }
 
 Footer.propTypes = {
     wide: PropTypes.bool,
 }
-
-const query = graphql`
-    query FooterQuery {
-        site {
-            siteMetadata {
-                company
-                companyUrl
-                footer {
-                    label
-                    items {
-                        text
-                        url
-                    }
-                }
-                newsletter {
-                    user
-                    id
-                    list
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/components/infobox.js b/website/src/components/infobox.js
index 6df8426b8..d17cbc285 100644
--- a/website/src/components/infobox.js
+++ b/website/src/components/infobox.js
@@ -29,7 +29,7 @@ export default function Infobox({
                         {variant !== 'default' && !emoji && (
                             <Icon width={18} name={variant} inline className={classes.icon} />
                         )}
-                        <span className={classes.titleText}>
+                        <span>
                             {emoji && (
                                 <span className={classes.emoji} aria-hidden="true">
                                     {emoji}
diff --git a/website/src/components/landing.js b/website/src/components/landing.js
index de054ddce..ec8cf88d6 100644
--- a/website/src/components/landing.js
+++ b/website/src/components/landing.js
@@ -53,7 +53,7 @@ export const LandingGrid = ({ cols = 3, blocks = false, style, children }) => (
     </Content>
 )
 
-export const LandingCol = ({ children }) => <div className={classes.col}>{children}</div>
+export const LandingCol = ({ children }) => <div>{children}</div>
 
 export const LandingCard = ({ title, button, url, children }) => (
     <div className={classes.card}>
diff --git a/website/src/components/link.js b/website/src/components/link.js
index 4f4442679..917b009b7 100644
--- a/website/src/components/link.js
+++ b/website/src/components/link.js
@@ -78,10 +78,10 @@ export default function Link({
         )
     }
     const isInternal = internalRegex.test(dest)
-    const rel = isInternal ? null : 'noopener nofollow noreferrer'
+    const relTarget = isInternal ? {} : { rel: 'noopener nofollow noreferrer', target: '_blank' }
     return (
         <Wrapper>
-            <a href={dest} className={linkClassNames} target="_blank" rel={rel} {...other}>
+            <a href={dest} className={linkClassNames} {...relTarget} {...other}>
                 {content}
             </a>
         </Wrapper>
diff --git a/website/src/components/quickstart.js b/website/src/components/quickstart.js
index 0ea88c621..e5d4e00be 100644
--- a/website/src/components/quickstart.js
+++ b/website/src/components/quickstart.js
@@ -152,10 +152,7 @@ const Quickstart = ({
                                                     type={optionType}
                                                     className={classNames(
                                                         classes.input,
-                                                        classes[optionType],
-                                                        {
-                                                            [classes.long]: options.length >= 4,
-                                                        }
+                                                        classes[optionType]
                                                     )}
                                                     name={id}
                                                     id={`quickstart-${option.id}`}
@@ -167,11 +164,7 @@ const Quickstart = ({
                                                     htmlFor={`quickstart-${option.id}`}
                                                 >
                                                     {option.title}
-                                                    {option.meta && (
-                                                        <span className={classes.meta}>
-                                                            {option.meta}
-                                                        </span>
-                                                    )}
+                                                    {option.meta && <span>{option.meta}</span>}
                                                     {option.help && (
                                                         <span
                                                             data-tooltip={option.help}
diff --git a/website/src/components/seo.js b/website/src/components/seo.js
index b32501948..a426317af 100644
--- a/website/src/components/seo.js
+++ b/website/src/components/seo.js
@@ -1,13 +1,14 @@
 import React from 'react'
 import PropTypes from 'prop-types'
 import Helmet from 'react-helmet'
-import { StaticQuery, graphql } from 'gatsby'
 
 import socialImageDefault from '../images/social_default.jpg'
 import socialImageApi from '../images/social_api.jpg'
 import socialImageUniverse from '../images/social_universe.jpg'
 import socialImageNightly from '../images/social_nightly.jpg'
 import socialImageLegacy from '../images/social_legacy.jpg'
+import siteMetadata from '../../meta/site.json'
+import { siteUrl } from '../../meta/dynamicMeta'
 
 function getPageTitle(title, sitename, slogan, sectionTitle, nightly, legacy) {
     if (sectionTitle && title) {
@@ -38,86 +39,78 @@ export default function SEO({
     nightly,
     legacy,
 }) {
-    return (
-        <StaticQuery
-            query={query}
-            render={(data) => {
-                const siteMetadata = data.site.siteMetadata
-                const metaDescription = description || siteMetadata.description
-                const pageTitle = getPageTitle(
-                    title,
-                    siteMetadata.title,
-                    siteMetadata.slogan,
-                    sectionTitle,
-                    nightly,
-                    legacy
-                )
-                const socialImage = siteMetadata.siteUrl + getImage(section, nightly, legacy)
-                const meta = [
-                    {
-                        name: 'description',
-                        content: metaDescription,
-                    },
-                    {
-                        property: 'og:title',
-                        content: pageTitle,
-                    },
-                    {
-                        property: 'og:description',
-                        content: metaDescription,
-                    },
-                    {
-                        property: 'og:type',
-                        content: `website`,
-                    },
-                    {
-                        property: 'og:site_name',
-                        content: title,
-                    },
-                    {
-                        property: 'og:image',
-                        content: socialImage,
-                    },
-                    {
-                        name: 'twitter:card',
-                        content: 'summary_large_image',
-                    },
-                    {
-                        name: 'twitter:image',
-                        content: socialImage,
-                    },
-                    {
-                        name: 'twitter:creator',
-                        content: `@${siteMetadata.social.twitter}`,
-                    },
-                    {
-                        name: 'twitter:site',
-                        content: `@${siteMetadata.social.twitter}`,
-                    },
-                    {
-                        name: 'twitter:title',
-                        content: pageTitle,
-                    },
-                    {
-                        name: 'twitter:description',
-                        content: metaDescription,
-                    },
-                    {
-                        name: 'docsearch:language',
-                        content: lang,
-                    },
-                ]
+    const metaDescription = description || siteMetadata.description
+    const pageTitle = getPageTitle(
+        title,
+        siteMetadata.title,
+        siteMetadata.slogan,
+        sectionTitle,
+        nightly,
+        legacy
+    )
+    const socialImage = siteUrl + getImage(section, nightly, legacy)
+    const meta = [
+        {
+            name: 'description',
+            content: metaDescription,
+        },
+        {
+            property: 'og:title',
+            content: pageTitle,
+        },
+        {
+            property: 'og:description',
+            content: metaDescription,
+        },
+        {
+            property: 'og:type',
+            content: `website`,
+        },
+        {
+            property: 'og:site_name',
+            content: title,
+        },
+        {
+            property: 'og:image',
+            content: socialImage,
+        },
+        {
+            name: 'twitter:card',
+            content: 'summary_large_image',
+        },
+        {
+            name: 'twitter:image',
+            content: socialImage,
+        },
+        {
+            name: 'twitter:creator',
+            content: `@${siteMetadata.social.twitter}`,
+        },
+        {
+            name: 'twitter:site',
+            content: `@${siteMetadata.social.twitter}`,
+        },
+        {
+            name: 'twitter:title',
+            content: pageTitle,
+        },
+        {
+            name: 'twitter:description',
+            content: metaDescription,
+        },
+        {
+            name: 'docsearch:language',
+            content: lang,
+        },
+    ]
 
-                return (
-                    <Helmet
-                        defer={false}
-                        htmlAttributes={{ lang }}
-                        bodyAttributes={{ class: bodyClass }}
-                        title={pageTitle}
-                        meta={meta}
-                    />
-                )
-            }}
+    return (
+        <Helmet
+            defer={false}
+            htmlAttributes={{ lang }}
+            bodyAttributes={{ class: bodyClass }}
+            title={pageTitle}
+            meta={meta}
         />
     )
 }
@@ -131,19 +124,3 @@ SEO.propTypes = {
     section: PropTypes.string,
     bodyClass: PropTypes.string,
 }
-
-const query = graphql`
-    query DefaultSEOQuery {
-        site {
-            siteMetadata {
-                title
-                description
-                slogan
-                siteUrl
-                social {
-                    twitter
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/components/typography.js b/website/src/components/typography.js
index 959dc0285..e05948ff5 100644
--- a/website/src/components/typography.js
+++ b/website/src/components/typography.js
@@ -70,12 +70,6 @@ export const Help = ({ children, className, size = 16 }) => (
     </span>
 )
 
-/**
- * Allows inserting comments that will appear in .md preview on GitHub, but
- * won't be included in the final build of the site.
- */
-export const Comment = () => null
-
 const Permalink = ({ id, children }) =>
     !id ? (
         <span className={headingTextClassName}>{children}</span>
diff --git a/website/src/components/util.js b/website/src/components/util.js
index 2b9c6fd4f..e3d5aef65 100644
--- a/website/src/components/util.js
+++ b/website/src/components/util.js
@@ -3,10 +3,11 @@ import { Parser as HtmlToReactParser } from 'html-to-react'
 import remark from 'remark'
 import remark2react from 'remark-react'
 import siteMetadata from '../../meta/site.json'
+import { domain } from '../../meta/dynamicMeta'
 
 const htmlToReactParser = new HtmlToReactParser()
 
-const isNightly = siteMetadata.nightlyBranches.includes(siteMetadata.domain)
+const isNightly = siteMetadata.nightlyBranches.includes(domain)
 export const DEFAULT_BRANCH = isNightly ? 'develop' : 'master'
 export const repo = siteMetadata.repo
 export const modelsRepo = siteMetadata.modelsRepo
diff --git a/website/src/pages/404.js b/website/src/pages/404.js
index 53baebab9..29ff29bca 100644
--- a/website/src/pages/404.js
+++ b/website/src/pages/404.js
@@ -1,16 +1,15 @@
 import React from 'react'
 import { window } from 'browser-monads'
-import { graphql } from 'gatsby'
 
 import Template from '../templates/index'
 import { LandingHeader, LandingTitle } from '../components/landing'
 import Button from '../components/button'
+import { nightly, legacy } from '../../meta/dynamicMeta'
 
-export default ({ data, location }) => {
-    const { nightly, legacy } = data.site.siteMetadata
+const page404 = ({ location }) => {
     const pageContext = { title: '404 Error', searchExclude: true, isIndex: false }
     return (
-        <Template data={data} pageContext={pageContext} location={location}>
+        <Template pageContext={pageContext} location={location}>
             <LandingHeader style={{ minHeight: 400 }} nightly={nightly} legacy={legacy}>
                 <LandingTitle>
                     Ooops, this page
@@ -26,24 +25,4 @@ export default ({ data, location }) => {
     )
 }
 
-export const pageQuery = graphql`
-    query {
-        site {
-            siteMetadata {
-                nightly
-                legacy
-                title
-                description
-                navigation {
-                    text
-                    url
-                }
-                docSearch {
-                    apiKey
-                    indexName
-                    appId
-                }
-            }
-        }
-    }
-`
+export default page404
diff --git a/website/src/remark.js b/website/src/remark.js
new file mode 100644
index 000000000..5c4f019a0
--- /dev/null
+++ b/website/src/remark.js
@@ -0,0 +1,61 @@
+import Link from './components/link'
+import Section, { Hr } from './components/section'
+import { Table, Tr, Th, Tx, Td } from './components/table'
+import { Pre, Code, InlineCode, TypeAnnotation } from './components/code'
+import { Ol, Ul, Li } from './components/list'
+import { H2, H3, H4, H5, P, Abbr, Help } from './components/typography'
+import Accordion from './components/accordion'
+import Infobox from './components/infobox'
+import Aside from './components/aside'
+import Button from './components/button'
+import Tag from './components/tag'
+import Grid from './components/grid'
+import { YouTube, SoundCloud, Iframe, Image, GoogleSheet } from './components/embed'
+import Project from './widgets/project'
+import { Integration, IntegrationLogo } from './widgets/integration'
+
+export const remarkComponents = {
+    a: Link,
+    p: P,
+    pre: Pre,
+    code: Code,
+    inlineCode: InlineCode,
+    del: TypeAnnotation,
+    table: Table,
+    img: Image,
+    tr: Tr,
+    th: Th,
+    td: Td,
+    ol: Ol,
+    ul: Ul,
+    li: Li,
+    h2: H2,
+    h3: H3,
+    h4: H4,
+    h5: H5,
+    blockquote: Aside,
+    section: Section,
+    wrapper: ({ children }) => children,
+    hr: Hr,
+
+    Infobox,
+    Table,
+    Tr,
+    Tx,
+    Th,
+    Td,
+    Help,
+    Button,
+    YouTube,
+    SoundCloud,
+    Iframe,
+    GoogleSheet,
+    Abbr,
+    Tag,
+    Accordion,
+    Grid,
+    InlineCode,
+    Project,
+    Integration,
+    IntegrationLogo,
+}
diff --git a/website/src/styles/aside.module.sass b/website/src/styles/aside.module.sass
index 1ea3f970a..aca74a33a 100644
--- a/website/src/styles/aside.module.sass
+++ b/website/src/styles/aside.module.sass
@@ -1,3 +1,4 @@
+@use 'sass:math'
 @import base
 
 $triangle-size: 2rem
@@ -55,14 +56,14 @@ $border-radius: 6px
         // Banner effect
         &:after
             position: absolute
-            bottom: -$triangle-size / 2
-            left: $border-radius / 2
+            bottom: math.div(-$triangle-size, 2)
+            left: math.div($border-radius, 2)
             width: 0
             height: 0
             border-color: transparent
             border-style: solid
             border-top-color: var(--color-dark)
-            border-width: $triangle-size / 2 0 0 calc(#{$triangle-size} - #{$border-radius / 2})
+            border-width: math.div($triangle-size, 2) 0 0 calc(#{$triangle-size} - #{math.div($border-radius, 2)})
             content: ""
 
 @include breakpoint(max, sm)
diff --git a/website/src/styles/code.module.sass b/website/src/styles/code.module.sass
index aa1f499dd..142b9fbd4 100644
--- a/website/src/styles/code.module.sass
+++ b/website/src/styles/code.module.sass
@@ -56,7 +56,7 @@
     --color-inline-code-text: var(--color-back)
     --color-inline-code-bg: var(--color-dark-secondary)
 
-.type-annotation,
+.type-annotation
     white-space: pre-wrap
     font-family: var(--font-code)
 
diff --git a/website/src/styles/embed.module.sass b/website/src/styles/embed.module.sass
index 1eaf7b8d2..fdb740951 100644
--- a/website/src/styles/embed.module.sass
+++ b/website/src/styles/embed.module.sass
@@ -1,3 +1,5 @@
+@use 'sass:math'
+
 .root
     margin-bottom: var(--spacing-md)
 
@@ -6,8 +8,8 @@
     height: 0
 
 @each $ratio1, $ratio2 in (16, 9), (4, 3)
-    &.ratio#{$ratio1}x#{$ratio2}
-        padding-bottom: (100% * $ratio2 / $ratio1)
+    .ratio#{$ratio1}x#{$ratio2}
+        padding-bottom: (100% * math.div($ratio2, $ratio1))
 
 .iframe
     position: absolute
diff --git a/website/src/styles/grid.module.sass b/website/src/styles/grid.module.sass
index 482ad03cf..7bf8ebd56 100644
--- a/website/src/styles/grid.module.sass
+++ b/website/src/styles/grid.module.sass
@@ -1,3 +1,4 @@
+@use 'sass:math'
 @import base
 
 $grid-gap-wide: 5rem
@@ -9,7 +10,7 @@ $flex-gap: 2rem
         @if $gap == 0
             flex: 0 0 100% / $cols
         @else
-            flex: 0 0 calc(#{100% / $cols} - #{$gap * ($cols - 1)})
+            flex: 0 0 calc(#{math.div(100%, $cols)} - #{$gap * ($cols - 1)})
 
 .root
     display: flex
diff --git a/website/src/styles/layout.sass b/website/src/styles/layout.sass
index b9b5c02dc..4df924b60 100644
--- a/website/src/styles/layout.sass
+++ b/website/src/styles/layout.sass
@@ -546,3 +546,34 @@ body [id]:target
 
     code, a
         color: inherit
+
+
+/* Algolia DocSearch */
+
+@include breakpoint(max, xs)
+    .algolia-autocomplete .ds-dropdown-menu
+        max-width: 90vw !important
+        min-width: 90vw !important
+
+.algolia-autocomplete .algolia-docsearch-suggestion--category-header
+    display: block
+    font: bold var(--font-size-lg)/var(--line-height-md) var(--font-secondary)
+    text-transform: uppercase
+    color: var(--color-theme)
+
+.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column
+    color: var(--color-dark)
+
+.algolia-autocomplete .algolia-docsearch-suggestion--title
+    font-size: var(--font-size-sm)
+
+.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content
+    background: var(--color-subtle-opaque) !important
+
+.algolia-autocomplete .algolia-docsearch-suggestion--text
+    font-size: var(--font-size-sm)
+
+.algolia-autocomplete .algolia-docsearch-suggestion--highlight
+    box-shadow: none !important
+    background: var(--color-theme-opaque) !important
+    color: var(--color-theme-dark) !important
diff --git a/website/src/styles/quickstart.module.sass b/website/src/styles/quickstart.module.sass
index d0f9db551..fb9f0b17b 100644
--- a/website/src/styles/quickstart.module.sass
+++ b/website/src/styles/quickstart.module.sass
@@ -1,3 +1,4 @@
+@use 'sass:math'
 @import base
 
 .root
@@ -45,7 +46,7 @@
     &:hover
         background: var(--color-subtle-light)
 
-    .input:focus +
+    .input:focus
         border: 1px solid var(--color-theme)
         outline: none
 
@@ -82,14 +83,14 @@
         vertical-align: middle
         margin-right: 0.5rem
         cursor: pointer
-        border-radius: $size / 4
+        border-radius: math.div($size, 4)
         background: var(--color-back)
         position: relative
         top: -1px
 
     .checkbox:checked + &:before
         // Embed "check" icon here for simplicity
-        background: var(--color-theme) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4gICAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTYuMTcybDEwLjU5NC0xMC41OTQgMS40MDYgMS40MDYtMTIgMTItNS41NzgtNS41NzggMS40MDYtMS40MDZ6Ii8+PC9zdmc+);
+        background: var(--color-theme) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4gICAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTYuMTcybDEwLjU5NC0xMC41OTQgMS40MDYgMS40MDYtMTIgMTItNS41NzgtNS41NzggMS40MDYtMS40MDZ6Ii8+PC9zdmc+)
         background-size: contain
         border-color: var(--color-theme)
 
diff --git a/website/src/styles/search.module.sass b/website/src/styles/search.module.sass
index 7a20ea821..8ac4d0b0e 100644
--- a/website/src/styles/search.module.sass
+++ b/website/src/styles/search.module.sass
@@ -26,33 +26,3 @@
     top: 0.25rem
     left: 0.15rem
     cursor: pointer
-
-/* Algolia DocSearch */
-
-@include breakpoint(max, xs)
-    \:global(.algolia-autocomplete .ds-dropdown-menu)
-        max-width: 90vw !important
-        min-width: 90vw !important
-
-\:global(.algolia-autocomplete .algolia-docsearch-suggestion--category-header)
-    display: block
-    font: bold var(--font-size-lg)/var(--line-height-md) var(--font-secondary)
-    text-transform: uppercase
-    color: var(--color-theme)
-
-\:global(.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column)
-    color: var(--color-dark)
-
-\:global(.algolia-autocomplete .algolia-docsearch-suggestion--title)
-    font-size: var(--font-size-sm)
-
-\:global(.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content)
-    background: var(--color-subtle-opaque) !important
-
-\:global(.algolia-autocomplete .algolia-docsearch-suggestion--text)
-    font-size: var(--font-size-sm)
-
-\:global(.algolia-autocomplete .algolia-docsearch-suggestion--highlight)
-    box-shadow: none !important
-    background: var(--color-theme-opaque) !important
-    color: var(--color-theme-dark) !important
diff --git a/website/src/styles/sidebar.module.sass b/website/src/styles/sidebar.module.sass
index ace19e6a4..36809dfbe 100644
--- a/website/src/styles/sidebar.module.sass
+++ b/website/src/styles/sidebar.module.sass
@@ -1,3 +1,4 @@
+@use 'sass:math'
 @import base
 
 $crumb-bullet: 14px
@@ -58,7 +59,7 @@ $crumb-bar: 2px
     position: relative
 
 .crumb
-    margin-bottom: $crumb-bullet / 2
+    margin-bottom: math.div($crumb-bullet, 2)
     position: relative
     padding-left: 2rem
     color: var(--color-theme)
@@ -71,7 +72,7 @@ $crumb-bar: 2px
         width: $crumb-bullet
         height: $crumb-bullet
         position: absolute
-        top: $crumb-bullet / 4
+        top: math.div($crumb-bullet, 4)
         left: 0
         content: ""
         border-radius: 50%
@@ -83,13 +84,13 @@ $crumb-bar: 2px
         height: 100%
         position: absolute
         top: $crumb-bullet
-        left: ($crumb-bullet - $crumb-bar) / 2
+        left: math.div(($crumb-bullet - $crumb-bar), 2)
         content: ""
         background: var(--color-subtle)
 
     &:first-child:before
         height: calc(100% + #{$crumb-bullet * 2})
-        top: -$crumb-bullet / 2
+        top: math.div(-$crumb-bullet, 2)
 
 .crumb-active
     color: var(--color-dark)
diff --git a/website/src/templates/docs.js b/website/src/templates/docs.js
index 6c684f725..c593e0360 100644
--- a/website/src/templates/docs.js
+++ b/website/src/templates/docs.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { StaticQuery, graphql } from 'gatsby'
 
 import Models from './models'
 
@@ -13,98 +12,99 @@ import Sidebar from '../components/sidebar'
 import Main from '../components/main'
 import { getCurrentSource, github } from '../components/util'
 
-const Docs = ({ pageContext, children }) => (
-    <StaticQuery
-        query={query}
-        render={({ site }) => {
-            const {
-                id,
-                slug,
-                title,
-                section,
-                teaser,
-                source,
-                tag,
-                isIndex,
-                next,
-                menu,
-                theme,
-                version,
-                apiDetails,
-            } = pageContext
-            const { sidebars = [], modelsRepo, languages, nightly, legacy } = site.siteMetadata
-            const isModels = section === 'models'
-            const sidebar = pageContext.sidebar
-                ? { items: pageContext.sidebar }
-                : sidebars.find((bar) => bar.section === section)
-            let pageMenu = menu ? menu.map(([text, id]) => ({ text, id })) : []
+import siteMetadata from '../../meta/site.json'
+import sidebars from '../../meta/sidebars.json'
+import models from '../../meta/languages.json'
+import { nightly, legacy } from '../../meta/dynamicMeta'
 
-            if (isModels) {
-                sidebar.items[1].items = languages
-                    .filter(({ models }) => models && models.length)
-                    .sort((a, b) => a.name.localeCompare(b.name))
-                    .map((lang) => ({
-                        text: lang.name,
-                        url: `/models/${lang.code}`,
-                        isActive: id === lang.code,
-                        menu: lang.models.map((model) => ({
-                            text: model,
-                            id: model,
-                        })),
-                    }))
-            }
-            const sourcePath = source ? github(source) : null
-            const currentSource = getCurrentSource(slug, isIndex)
+const Docs = ({ pageContext, children }) => {
+    const {
+        id,
+        slug,
+        title,
+        section,
+        teaser,
+        source,
+        tag,
+        isIndex,
+        next,
+        menu,
+        theme,
+        version,
+        apiDetails,
+    } = pageContext
+    const { modelsRepo } = siteMetadata
+    const { languages } = models
+    const isModels = section === 'models'
+    const sidebar = pageContext.sidebar
+        ? { items: pageContext.sidebar }
+        : sidebars.find((bar) => bar.section === section)
+    let pageMenu = menu ? menu.map(([text, id]) => ({ text, id })) : []
 
-            const subFooter = (
-                <Grid cols={2}>
-                    <div style={{ marginTop: 'var(--spacing-lg)' }}>
-                        {(!isModels || (isModels && isIndex)) && (
-                            <Button to={currentSource} icon="code">
-                                Suggest edits
-                            </Button>
-                        )}
-                    </div>
-                    {next && <ReadNext title={next.title} to={next.slug} />}
-                </Grid>
-            )
+    if (isModels) {
+        sidebar.items[1].items = languages
+            .filter(({ models }) => models && models.length)
+            .sort((a, b) => a.name.localeCompare(b.name))
+            .map((lang) => ({
+                text: lang.name,
+                url: `/models/${lang.code}`,
+                isActive: id === lang.code,
+                menu: lang.models.map((model) => ({
+                    text: model,
+                    id: model,
+                })),
+            }))
+    }
+    const sourcePath = source ? github(source) : null
+    const currentSource = getCurrentSource(slug, isIndex)
 
-            return (
-                <>
-                    {sidebar && <Sidebar items={sidebar.items} pageMenu={pageMenu} slug={slug} />}
-                    <Main
-                        section={section}
-                        theme={nightly ? 'nightly' : legacy ? 'legacy' : theme}
-                        sidebar
-                        asides
-                        wrapContent
-                        footer={<Footer />}
-                    >
-                        {isModels && !isIndex ? (
-                            <Models pageContext={pageContext} repo={modelsRepo}>
-                                {subFooter}
-                            </Models>
-                        ) : (
-                            <>
-                                <Title
-                                    title={title}
-                                    teaser={teaser}
-                                    source={sourcePath}
-                                    tag={tag}
-                                    version={version}
-                                    id="_title"
-                                    apiDetails={apiDetails}
-                                />
-                                {children}
-                                {subFooter}
-                            </>
-                        )}
-                    </Main>
-                </>
-            )
-        }}
-    />
-)
+    const subFooter = (
+        <Grid cols={2}>
+            <div style={{ marginTop: 'var(--spacing-lg)' }}>
+                {(!isModels || (isModels && isIndex)) && (
+                    <Button to={currentSource} icon="code">
+                        Suggest edits
+                    </Button>
+                )}
+            </div>
+            {next && <ReadNext title={next.title} to={next.slug} />}
+        </Grid>
+    )
+
+    return (
+        <>
+            {sidebar && <Sidebar items={sidebar.items} pageMenu={pageMenu} slug={slug} />}
+            <Main
+                section={section}
+                theme={nightly ? 'nightly' : legacy ? 'legacy' : theme}
+                sidebar
+                asides
+                wrapContent
+                footer={<Footer />}
+            >
+                {isModels && !isIndex ? (
+                    <Models pageContext={pageContext} repo={modelsRepo}>
+                        {subFooter}
+                    </Models>
+                ) : (
+                    <>
+                        <Title
+                            title={title}
+                            teaser={teaser}
+                            source={sourcePath}
+                            tag={tag}
+                            version={version}
+                            id="_title"
+                            apiDetails={apiDetails}
+                        />
+                        {children}
+                        {subFooter}
+                    </>
+                )}
+            </Main>
+        </>
+    )
+}
 
 Docs.propTypes = {
     pageContext: PropTypes.shape({
@@ -125,32 +125,3 @@ Docs.propTypes = {
 }
 
 export default Docs
-
-const query = graphql`
-    query DocsQuery {
-        site {
-            siteMetadata {
-                repo
-                modelsRepo
-                languages {
-                    code
-                    name
-                    models
-                }
-                nightly
-                legacy
-                sidebars {
-                    section
-                    items {
-                        label
-                        items {
-                            text
-                            url
-                            tag
-                        }
-                    }
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/templates/index.js b/website/src/templates/index.js
index 438e5b91e..0b48f6e93 100644
--- a/website/src/templates/index.js
+++ b/website/src/templates/index.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { graphql } from 'gatsby'
 import { MDXProvider } from '@mdx-js/tag'
 import { withMDXScope } from 'gatsby-mdx/context'
 import useOnlineStatus from '@rehooks/online-status'
@@ -18,70 +17,13 @@ import Progress from '../components/progress'
 import Footer from '../components/footer'
 import SEO from '../components/seo'
 import Link from '../components/link'
-import Section, { Hr } from '../components/section'
-import { Table, Tr, Th, Tx, Td } from '../components/table'
-import { Pre, Code, InlineCode, TypeAnnotation } from '../components/code'
-import { Ol, Ul, Li } from '../components/list'
-import { H2, H3, H4, H5, P, Abbr, Help } from '../components/typography'
-import Accordion from '../components/accordion'
-import Infobox from '../components/infobox'
-import Aside from '../components/aside'
-import Button from '../components/button'
-import Tag from '../components/tag'
-import Grid from '../components/grid'
-import { YouTube, SoundCloud, Iframe, Image, GoogleSheet } from '../components/embed'
+import { InlineCode } from '../components/code'
 import Alert from '../components/alert'
 import Search from '../components/search'
-import Project from '../widgets/project'
-import { Integration, IntegrationLogo } from '../widgets/integration'
 
-const mdxComponents = {
-    a: Link,
-    p: P,
-    pre: Pre,
-    code: Code,
-    inlineCode: InlineCode,
-    del: TypeAnnotation,
-    table: Table,
-    img: Image,
-    tr: Tr,
-    th: Th,
-    td: Td,
-    ol: Ol,
-    ul: Ul,
-    li: Li,
-    h2: H2,
-    h3: H3,
-    h4: H4,
-    h5: H5,
-    blockquote: Aside,
-    section: Section,
-    wrapper: ({ children }) => children,
-    hr: Hr,
-}
-
-const scopeComponents = {
-    Infobox,
-    Table,
-    Tr,
-    Tx,
-    Th,
-    Td,
-    Help,
-    Button,
-    YouTube,
-    SoundCloud,
-    Iframe,
-    GoogleSheet,
-    Abbr,
-    Tag,
-    Accordion,
-    Grid,
-    InlineCode,
-    Project,
-    Integration,
-    IntegrationLogo,
-}
+import siteMetadata from '../../meta/site.json'
+import { nightly, legacy } from '../../meta/dynamicMeta'
+import { remarkComponents } from '../remark'
 
 const AlertSpace = ({ nightly, legacy }) => {
     const isOnline = useOnlineStatus()
@@ -112,7 +54,7 @@ const AlertSpace = ({ nightly, legacy }) => {
             )}
             {!isOnline && (
                 <Alert title="Looks like you're offline." icon="offline" variant="warning">
-                    But don't worry, your visited pages should be saved for you.
+                    But don&apos;t worry, your visited pages should be saved for you.
                 </Alert>
             )}
         </>
@@ -160,20 +102,19 @@ class Layout extends React.Component {
         // NB: Compiling the scope here instead of in render() is super
         // important! Otherwise, it triggers unnecessary rerenders of ALL
         // consumers (e.g. mdx elements), even on anchor navigation!
-        this.state = { scope: { ...scopeComponents, ...props.scope } }
+        this.state = { scope: { ...remarkComponents, ...props.scope } }
     }
 
     render() {
         const { data, pageContext, location, children } = this.props
         const { file, site = {} } = data || {}
         const mdx = file ? file.childMdx : null
-        const meta = site.siteMetadata || {}
         const { title, section, sectionTitle, teaser, theme = 'blue', searchExclude } = pageContext
-        const uiTheme = meta.nightly ? 'nightly' : meta.legacy ? 'legacy' : theme
+        const uiTheme = nightly ? 'nightly' : legacy ? 'legacy' : theme
         const bodyClass = classNames(`theme-${uiTheme}`, { 'search-exclude': !!searchExclude })
         const isDocs = ['usage', 'models', 'api', 'styleguide'].includes(section)
         const content = !mdx ? null : (
-            <MDXProvider components={mdxComponents}>
+            <MDXProvider components={remarkComponents}>
                 <MDXRenderer scope={this.state.scope}>{mdx.code.body}</MDXRenderer>
             </MDXProvider>
         )
@@ -182,21 +123,21 @@ class Layout extends React.Component {
             <>
                 <SEO
                     title={title}
-                    description={teaser || meta.description}
+                    description={teaser || siteMetadata.description}
                     section={section}
                     sectionTitle={sectionTitle}
                     bodyClass={bodyClass}
-                    nightly={meta.nightly}
+                    nightly={nightly}
                 />
-                <AlertSpace nightly={meta.nightly} legacy={meta.legacy} />
+                <AlertSpace nightly={nightly} legacy={legacy} />
                 <Navigation
-                    title={meta.title}
-                    items={meta.navigation}
+                    title={siteMetadata.title}
+                    items={siteMetadata.navigation}
                     section={section}
-                    search={<Search settings={meta.docSearch} />}
-                    alert={meta.nightly ? null : navAlert}
+                    search={<Search settings={siteMetadata.docSearch} />}
+                    alert={nightly ? null : navAlert}
                 >
-                    <Progress key={location.href} />
+                    <Progress />
                 </Navigation>
                 {isDocs ? (
                     <Docs pageContext={pageContext}>{content}</Docs>
@@ -219,33 +160,3 @@ class Layout extends React.Component {
 }
 
 export default withMDXScope(Layout)
-
-export const pageQuery = graphql`
-    query ($slug: String!) {
-        site {
-            siteMetadata {
-                nightly
-                legacy
-                title
-                description
-                navigation {
-                    text
-                    url
-                }
-                docSearch {
-                    apiKey
-                    indexName
-                    appId
-                }
-            }
-        }
-        file(fields: { slug: { eq: $slug } }) {
-            childMdx {
-                code {
-                    scope
-                    body
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/templates/models.js b/website/src/templates/models.js
index e19edda8b..d946d5836 100644
--- a/website/src/templates/models.js
+++ b/website/src/templates/models.js
@@ -1,5 +1,4 @@
 import React, { useEffect, useState, useMemo, Fragment } from 'react'
-import { StaticQuery, graphql } from 'gatsby'
 import { window } from 'browser-monads'
 
 import Title from '../components/title'
@@ -17,6 +16,9 @@ import Accordion from '../components/accordion'
 import { join, arrayToObj, abbrNum, markdownToReact } from '../components/util'
 import { isString, isEmptyObj } from '../components/util'
 
+import siteMetadata from '../../meta/site.json'
+import languages from '../../meta/languages.json'
+
 const COMPONENT_LINKS = {
     tok2vec: '/api/tok2vec',
     transformer: '/api/transformer',
@@ -367,7 +369,7 @@ const Model = ({
                                 const labelNames = labels[pipe] || []
                                 const help = LABEL_SCHEME_META[pipe]
                                 return (
-                                    <Tr key={`${name}-${pipe}`} evenodd={false} key={pipe}>
+                                    <Tr evenodd={false} key={pipe}>
                                         <Td style={{ width: '20%' }}>
                                             <Label>
                                                 {pipe} {help && <Help>{help}</Help>}
@@ -415,43 +417,23 @@ const Models = ({ pageContext, repo, children }) => {
     return (
         <>
             <Title title={title} teaser={`Available trained pipelines for ${title}`} />
-            <StaticQuery
-                query={query}
-                render={({ site }) =>
-                    models.map((modelName) => (
-                        <Model
-                            key={modelName}
-                            name={modelName}
-                            langId={id}
-                            langName={title}
-                            compatibility={compatibility}
-                            baseUrl={baseUrl}
-                            repo={repo}
-                            licenses={arrayToObj(site.siteMetadata.licenses, 'id')}
-                            hasExamples={meta.hasExamples}
-                            prereleases={site.siteMetadata.nightly}
-                        />
-                    ))
-                }
-            />
-
+            {models.map((modelName) => (
+                <Model
+                    key={modelName}
+                    name={modelName}
+                    langId={id}
+                    langName={title}
+                    compatibility={compatibility}
+                    baseUrl={baseUrl}
+                    repo={repo}
+                    licenses={arrayToObj(languages.licenses, 'id')}
+                    hasExamples={meta.hasExamples}
+                    prereleases={siteMetadata.nightly}
+                />
+            ))}
             {children}
         </>
     )
 }
 
 export default Models
-
-const query = graphql`
-    query ModelsQuery {
-        site {
-            siteMetadata {
-                nightly
-                licenses {
-                    id
-                    url
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/templates/universe.js b/website/src/templates/universe.js
index 6db6d0fb2..80f3d25df 100644
--- a/website/src/templates/universe.js
+++ b/website/src/templates/universe.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { StaticQuery, graphql } from 'gatsby'
 
 import Card from '../components/card'
 import Link from '../components/link'
@@ -19,6 +18,9 @@ import { H3, H5, Label, InlineList } from '../components/typography'
 import { YouTube, SoundCloud, Iframe } from '../components/embed'
 import { github, markdownToReact } from '../components/util'
 
+import { nightly, legacy } from '../../meta/dynamicMeta'
+import universe from '../../meta/universe.json'
+
 function getSlug(data) {
     if (data.isCategory) return `/universe/category/${data.id}`
     if (data.isProject) return `/universe/project/${data.id}`
@@ -123,9 +125,9 @@ const UniverseContent = ({ content = [], categories, theme, pageContext, mdxComp
                     </Section>
                 )}
                 <section className="search-exclude">
-                    <H3>Found a mistake or something isn't working?</H3>
+                    <H3>Found a mistake or something isn&apos;t working?</H3>
                     <p>
-                        If you've come across a universe project that isn't working or is
+                        If you&apos;ve come across a universe project that isn&apos;t working or is
                         incompatible with the reported spaCy version, let us know by{' '}
                         <Link to="https://github.com/explosion/spaCy/discussions/new">
                             opening a discussion thread
@@ -234,7 +236,7 @@ const Project = ({ data, components }) => (
         )}
         {data.cran && (
             <Aside title="Installation">
-                <CodeBlock lang="r">install.packages("{data.cran}")</CodeBlock>
+                <CodeBlock lang="r">install.packages(&quot;{data.cran}&quot;)</CodeBlock>
             </Aside>
         )}
 
@@ -333,73 +335,18 @@ const Project = ({ data, components }) => (
     </>
 )
 
-const Universe = ({ pageContext, location, mdxComponents }) => (
-    <StaticQuery
-        query={query}
-        render={(data) => {
-            const { universe, nightly, legacy } = data.site.siteMetadata
-            const theme = nightly ? 'nightly' : legacy ? 'legacy' : pageContext.theme
-            return (
-                <UniverseContent
-                    content={universe.resources}
-                    categories={universe.categories}
-                    pageContext={pageContext}
-                    location={location}
-                    mdxComponents={mdxComponents}
-                    theme={theme}
-                />
-            )
-        }}
-    />
-)
+const Universe = ({ pageContext, location, mdxComponents }) => {
+    const theme = nightly ? 'nightly' : legacy ? 'legacy' : pageContext.theme
+    return (
+        <UniverseContent
+            content={universe.resources}
+            categories={universe.categories}
+            pageContext={pageContext}
+            location={location}
+            mdxComponents={mdxComponents}
+            theme={theme}
+        />
+    )
+}
 
 export default Universe
-
-const query = graphql`
-    query UniverseQuery {
-        site {
-            siteMetadata {
-                nightly
-                legacy
-                universe {
-                    resources {
-                        type
-                        id
-                        title
-                        slogan
-                        url
-                        github
-                        description
-                        spacy_version
-                        pip
-                        cran
-                        category
-                        thumb
-                        image
-                        cover
-                        code_example
-                        code_language
-                        youtube
-                        soundcloud
-                        iframe
-                        iframe_height
-                        author
-                        author_links {
-                            twitter
-                            github
-                            website
-                        }
-                    }
-                    categories {
-                        label
-                        items {
-                            id
-                            title
-                            description
-                        }
-                    }
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/widgets/features.js b/website/src/widgets/features.js
index 73863d5cc..23864d1eb 100644
--- a/website/src/widgets/features.js
+++ b/website/src/widgets/features.js
@@ -1,72 +1,65 @@
 import React from 'react'
-import { graphql, StaticQuery } from 'gatsby'
 
 import { Ul, Li } from '../components/list'
 
-export default () => (
-    <StaticQuery
-        query={query}
-        render={({ site }) => {
-            const { counts } = site.siteMetadata
-            return (
-                <Ul>
-                    <Li>
-                        ✅ Support for <strong>{counts.langs}+ languages</strong>
-                    </Li>
-                    <Li>
-                        ✅ <strong>{counts.models} trained pipelines</strong> for{' '}
-                        {counts.modelLangs} languages
-                    </Li>
-                    <Li>
-                        ✅ Multi-task learning with pretrained <strong>transformers</strong> like
-                        BERT
-                    </Li>
-                    <Li>
-                        ✅ Pretrained <strong>word vectors</strong>
-                    </Li>
-                    <Li>✅ State-of-the-art speed</Li>
-                    <Li>
-                        ✅ Production-ready <strong>training system</strong>
-                    </Li>
-                    <Li>
-                        ✅ Linguistically-motivated <strong>tokenization</strong>
-                    </Li>
-                    <Li>
-                        ✅ Components for <strong>named entity</strong> recognition, part-of-speech
-                        tagging, dependency parsing, sentence segmentation,{' '}
-                        <strong>text classification</strong>, lemmatization, morphological analysis,
-                        entity linking and more
-                    </Li>
-                    <Li>
-                        ✅ Easily extensible with <strong>custom components</strong> and attributes
-                    </Li>
-                    <Li>
-                        ✅ Support for custom models in <strong>PyTorch</strong>,{' '}
-                        <strong>TensorFlow</strong> and other frameworks
-                    </Li>
-                    <Li>
-                        ✅ Built in <strong>visualizers</strong> for syntax and NER
-                    </Li>
-                    <Li>
-                        ✅ Easy <strong>model packaging</strong>, deployment and workflow management
-                    </Li>
-                    <Li>✅ Robust, rigorously evaluated accuracy</Li>
-                </Ul>
-            )
-        }}
-    />
-)
+import models from '../../meta/languages.json'
 
-const query = graphql`
-    query FeaturesQuery {
-        site {
-            siteMetadata {
-                counts {
-                    langs
-                    modelLangs
-                    models
-                }
-            }
-        }
+/**
+ * Compute the overall total counts of models and languages
+ */
+function getCounts(langs = []) {
+    return {
+        langs: langs.length,
+        modelLangs: langs.filter(({ models }) => models && !!models.length).length,
+        models: langs.map(({ models }) => (models ? models.length : 0)).reduce((a, b) => a + b, 0),
     }
-`
+}
+
+const Features = () => {
+    const counts = getCounts(models.languages)
+    return (
+        <Ul>
+            <Li>
+                ✅ Support for <strong>{counts.langs}+ languages</strong>
+            </Li>
+            <Li>
+                ✅ <strong>{counts.models} trained pipelines</strong> for {counts.modelLangs}{' '}
+                languages
+            </Li>
+            <Li>
+                ✅ Multi-task learning with pretrained <strong>transformers</strong> like BERT
+            </Li>
+            <Li>
+                ✅ Pretrained <strong>word vectors</strong>
+            </Li>
+            <Li>✅ State-of-the-art speed</Li>
+            <Li>
+                ✅ Production-ready <strong>training system</strong>
+            </Li>
+            <Li>
+                ✅ Linguistically-motivated <strong>tokenization</strong>
+            </Li>
+            <Li>
+                ✅ Components for <strong>named entity</strong> recognition, part-of-speech tagging,
+                dependency parsing, sentence segmentation, <strong>text classification</strong>,
+                lemmatization, morphological analysis, entity linking and more
+            </Li>
+            <Li>
+                ✅ Easily extensible with <strong>custom components</strong> and attributes
+            </Li>
+            <Li>
+                ✅ Support for custom models in <strong>PyTorch</strong>,{' '}
+                <strong>TensorFlow</strong> and other frameworks
+            </Li>
+            <Li>
+                ✅ Built in <strong>visualizers</strong> for syntax and NER
+            </Li>
+            <Li>
+                ✅ Easy <strong>model packaging</strong>, deployment and workflow management
+            </Li>
+            <Li>✅ Robust, rigorously evaluated accuracy</Li>
+        </Ul>
+    )
+}
+
+export default Features
diff --git a/website/src/widgets/landing.js b/website/src/widgets/landing.js
index 5d1ca1432..3fb45414f 100644
--- a/website/src/widgets/landing.js
+++ b/website/src/widgets/landing.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { StaticQuery, graphql } from 'gatsby'
 
 import {
     LandingHeader,
@@ -19,13 +18,15 @@ import { Ul, Li } from '../components/list'
 import Button from '../components/button'
 import Link from '../components/link'
 
-import QuickstartTraining from './quickstart-training'
-import Project from './project'
-import Features from './features'
+import QuickstartTraining from '../widgets/quickstart-training'
+import Project from '../widgets/project'
+import Features from '../widgets/features'
+import Layout from '../components/layout'
 import courseImage from '../../docs/images/course.jpg'
 import prodigyImage from '../../docs/images/prodigy_overview.jpg'
 import projectsImage from '../../docs/images/projects.png'
 import tailoredPipelinesImage from '../../docs/images/spacy-tailored-pipelines_wide.png'
+import { nightly, legacy } from '../../meta/dynamicMeta'
 
 import Benchmarks from '../../docs/usage/_benchmarks-models.mdx'
 
@@ -56,8 +57,7 @@ for entity in doc.ents:
 `
 }
 
-const Landing = ({ data }) => {
-    const { nightly, legacy } = data
+const Landing = () => {
     const codeExample = getCodeExample(nightly)
     return (
         <>
@@ -75,15 +75,15 @@ const Landing = ({ data }) => {
                 <LandingCard title="Get things done" url="/usage/spacy-101" button="Get started">
                     spaCy is designed to help you do real work — to build real products, or gather
                     real insights. The library respects your time, and tries to avoid wasting it.
-                    It's easy to install, and its API is simple and productive.
+                    It&apos;s easy to install, and its API is simple and productive.
                 </LandingCard>
                 <LandingCard
                     title="Blazing fast"
                     url="/usage/facts-figures"
                     button="Facts &amp; Figures"
                 >
-                    spaCy excels at large-scale information extraction tasks. It's written from the
-                    ground up in carefully memory-managed Cython. If your application needs to
+                    spaCy excels at large-scale information extraction tasks. It&apos;s written from
+                    the ground up in carefully memory-managed Cython. If your application needs to
                     process entire web dumps, spaCy is the library you want to be using.
                 </LandingCard>
 
@@ -115,33 +115,33 @@ const Landing = ({ data }) => {
                         <img src={tailoredPipelinesImage} alt="spaCy Tailored Pipelines" />
                     </Link>
                     <strong>
-                        Get a custom spaCy pipeline, tailor-made for your NLP problem by spaCy's
-                        core developers.
+                        Get a custom spaCy pipeline, tailor-made for your NLP problem by
+                        spaCy&apos;s core developers.
                     </strong>
                     <br />
                     <br />
                     <Ul>
                         <Li emoji="🔥">
                             <strong>Streamlined.</strong> Nobody knows spaCy better than we do. Send
-                            us your pipeline requirements and we'll be ready to start producing your
-                            solution in no time at all.
+                            us your pipeline requirements and we&apos;ll be ready to start producing
+                            your solution in no time at all.
                         </Li>
                         <Li emoji="🐿 ">
                             <strong>Production ready.</strong> spaCy pipelines are robust and easy
-                            to deploy. You'll get a complete spaCy project folder which is ready to{' '}
-                            <InlineCode>spacy project run</InlineCode>.
+                            to deploy. You&apos;ll get a complete spaCy project folder which is
+                            ready to <InlineCode>spacy project run</InlineCode>.
                         </Li>
                         <Li emoji="🔮">
-                            <strong>Predictable.</strong> You'll know exactly what you're going to
-                            get and what it's going to cost. We quote fees up-front, let you try
-                            before you buy, and don't charge for over-runs at our end — all the risk
-                            is on us.
+                            <strong>Predictable.</strong> You&apos;ll know exactly what you&apos;re
+                            going to get and what it&apos;s going to cost. We quote fees up-front,
+                            let you try before you buy, and don&apos;t charge for over-runs at our
+                            end — all the risk is on us.
                         </Li>
                         <Li emoji="🛠">
-                            <strong>Maintainable.</strong> spaCy is an industry standard, and we'll
-                            deliver your pipeline with full code, data, tests and documentation, so
-                            your team can retrain, update and extend the solution as your
-                            requirements change.
+                            <strong>Maintainable.</strong> spaCy is an industry standard, and
+                            we&apos;ll deliver your pipeline with full code, data, tests and
+                            documentation, so your team can retrain, update and extend the solution
+                            as your requirements change.
                         </Li>
                     </Ul>
                 </LandingBanner>
@@ -166,7 +166,7 @@ const Landing = ({ data }) => {
                     <br />
                     Prodigy is an <strong>annotation tool</strong> so efficient that data scientists
                     can do the annotation themselves, enabling a new level of rapid iteration.
-                    Whether you're working on entity recognition, intent detection or image
+                    Whether you&apos;re working on entity recognition, intent detection or image
                     classification, Prodigy can help you <strong>train and evaluate</strong> your
                     models faster.
                 </LandingBanner>
@@ -214,7 +214,7 @@ const Landing = ({ data }) => {
                 <LandingCol>
                     <H2>End-to-end workflows from prototype to production</H2>
                     <p>
-                        spaCy's new project system gives you a smooth path from prototype to
+                        spaCy&apos;s new project system gives you a smooth path from prototype to
                         production. It lets you keep track of all those{' '}
                         <strong>data transformation</strong>, preprocessing and{' '}
                         <strong>training steps</strong>, so you can make sure your project is always
@@ -237,11 +237,11 @@ const Landing = ({ data }) => {
                     small
                 >
                     spaCy v3.0 features all new <strong>transformer-based pipelines</strong> that
-                    bring spaCy's accuracy right up to the current <strong>state-of-the-art</strong>
-                    . You can use any pretrained transformer to train your own pipelines, and even
-                    share one transformer between multiple components with{' '}
-                    <strong>multi-task learning</strong>. Training is now fully configurable and
-                    extensible, and you can define your own custom models using{' '}
+                    bring spaCy&apos;s accuracy right up to the current{' '}
+                    <strong>state-of-the-art</strong>. You can use any pretrained transformer to
+                    train your own pipelines, and even share one transformer between multiple
+                    components with <strong>multi-task learning</strong>. Training is now fully
+                    configurable and extensible, and you can define your own custom models using{' '}
                     <strong>PyTorch</strong>, <strong>TensorFlow</strong> and other frameworks.
                 </LandingBanner>
                 <LandingBanner
@@ -271,7 +271,7 @@ const Landing = ({ data }) => {
                 <LandingCol>
                     <H2>Benchmarks</H2>
                     <p>
-                        spaCy v3.0 introduces transformer-based pipelines that bring spaCy's
+                        spaCy v3.0 introduces transformer-based pipelines that bring spaCy&apos;s
                         accuracy right up to the current <strong>state-of-the-art</strong>. You can
                         also use a CPU-optimized pipeline, which is less accurate but much cheaper
                         to run.
@@ -289,29 +289,4 @@ const Landing = ({ data }) => {
     )
 }
 
-Landing.propTypes = {
-    data: PropTypes.shape({
-        repo: PropTypes.string,
-        languages: PropTypes.arrayOf(
-            PropTypes.shape({
-                models: PropTypes.arrayOf(PropTypes.string),
-            })
-        ),
-    }),
-}
-
-export default () => (
-    <StaticQuery query={landingQuery} render={({ site }) => <Landing data={site.siteMetadata} />} />
-)
-
-const landingQuery = graphql`
-    query LandingQuery {
-        site {
-            siteMetadata {
-                nightly
-                legacy
-                repo
-            }
-        }
-    }
-`
+export default Landing
diff --git a/website/src/widgets/languages.js b/website/src/widgets/languages.js
index 14c3f23bf..9f5f9e23f 100644
--- a/website/src/widgets/languages.js
+++ b/website/src/widgets/languages.js
@@ -1,5 +1,4 @@
 import React from 'react'
-import { StaticQuery, graphql } from 'gatsby'
 
 import Link from '../components/link'
 import { InlineCode } from '../components/code'
@@ -8,6 +7,8 @@ import { Ul, Li } from '../components/list'
 import Infobox from '../components/infobox'
 import { github, join } from '../components/util'
 
+import models from '../../meta/languages.json'
+
 const Language = ({ name, code, models }) => (
     <Tr>
         <Td>{name}</Td>
@@ -31,75 +32,54 @@ const Language = ({ name, code, models }) => (
     </Tr>
 )
 
-const Languages = () => (
-    <StaticQuery
-        query={query}
-        render={({ site }) => {
-            const langs = site.siteMetadata.languages
-            const withModels = langs
-                .filter(({ models }) => models && !!models.length)
-                .sort((a, b) => a.name.localeCompare(b.name))
-            const withoutModels = langs
-                .filter(({ models }) => !models || !models.length)
-                .sort((a, b) => a.name.localeCompare(b.name))
-            const withDeps = langs.filter(({ dependencies }) => dependencies && dependencies.length)
-            return (
-                <>
-                    <Table>
-                        <thead>
-                            <Tr>
-                                <Th>Language</Th>
-                                <Th>Code</Th>
-                                <Th>Language Data</Th>
-                                <Th>Pipelines</Th>
-                            </Tr>
-                        </thead>
-                        <tbody>
-                            {withModels.map((model) => (
-                                <Language {...model} key={model.code} />
-                            ))}
-                            {withoutModels.map((model) => (
-                                <Language {...model} key={model.code} />
-                            ))}
-                        </tbody>
-                    </Table>
-                    <Infobox title="Dependencies">
-                        <p>Some language tokenizers require external dependencies.</p>
-                        <Ul>
-                            {withDeps.map(({ code, name, dependencies }) => (
-                                <Li key={code}>
-                                    <strong>{name}:</strong>{' '}
-                                    {join(
-                                        dependencies.map((dep, i) => (
-                                            <Link to={dep.url}>{dep.name}</Link>
-                                        ))
-                                    )}
-                                </Li>
-                            ))}
-                        </Ul>
-                    </Infobox>
-                </>
-            )
-        }}
-    />
-)
+const Languages = () => {
+    const langs = models.languages
+    const withModels = langs
+        .filter(({ models }) => models && !!models.length)
+        .sort((a, b) => a.name.localeCompare(b.name))
+    const withoutModels = langs
+        .filter(({ models }) => !models || !models.length)
+        .sort((a, b) => a.name.localeCompare(b.name))
+    const withDeps = langs.filter(({ dependencies }) => dependencies && dependencies.length)
+    return (
+        <>
+            <Table>
+                <thead>
+                    <Tr>
+                        <Th>Language</Th>
+                        <Th>Code</Th>
+                        <Th>Language Data</Th>
+                        <Th>Pipelines</Th>
+                    </Tr>
+                </thead>
+                <tbody>
+                    {withModels.map((model) => (
+                        <Language {...model} key={model.code} />
+                    ))}
+                    {withoutModels.map((model) => (
+                        <Language {...model} key={model.code} />
+                    ))}
+                </tbody>
+            </Table>
+            <Infobox title="Dependencies">
+                <p>Some language tokenizers require external dependencies.</p>
+                <Ul>
+                    {withDeps.map(({ code, name, dependencies }) => (
+                        <Li key={code}>
+                            <strong>{name}:</strong>{' '}
+                            {join(
+                                dependencies.map((dep, i) => (
+                                    <Link key={i} to={dep.url}>
+                                        {dep.name}
+                                    </Link>
+                                ))
+                            )}
+                        </Li>
+                    ))}
+                </Ul>
+            </Infobox>
+        </>
+    )
+}
 
 export default Languages
-
-const query = graphql`
-    query LanguagesQuery {
-        site {
-            siteMetadata {
-                languages {
-                    code
-                    name
-                    models
-                    dependencies {
-                        name
-                        url
-                    }
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/widgets/quickstart-install.js b/website/src/widgets/quickstart-install.js
index a64188e16..dbbbf0fe5 100644
--- a/website/src/widgets/quickstart-install.js
+++ b/website/src/widgets/quickstart-install.js
@@ -1,8 +1,9 @@
 import React, { useState } from 'react'
-import { StaticQuery, graphql } from 'gatsby'
 
 import { Quickstart, QS } from '../components/quickstart'
 import { repo, DEFAULT_BRANCH } from '../components/util'
+import siteMetadata from '../../meta/site.json'
+import models from '../../meta/languages.json'
 
 const DEFAULT_OS = 'mac'
 const DEFAULT_PLATFORM = 'x86'
@@ -51,215 +52,178 @@ const QuickstartInstall = ({ id, title }) => {
     ]
         .filter((e) => e)
         .join(',')
-    return (
-        <StaticQuery
-            query={query}
-            render={({ site }) => {
-                const { nightly, languages } = site.siteMetadata
-                const pkg = nightly ? 'spacy-nightly' : 'spacy'
-                const models = languages.filter(({ models }) => models !== null)
-                const data = [
-                    {
-                        id: 'os',
-                        title: 'Operating system',
-                        options: [
-                            { id: 'mac', title: 'macOS / OSX', checked: true },
-                            { id: 'windows', title: 'Windows' },
-                            { id: 'linux', title: 'Linux' },
-                        ],
-                        defaultValue: DEFAULT_OS,
-                    },
-                    {
-                        id: 'platform',
-                        title: 'Platform',
-                        options: [
-                            { id: 'x86', title: 'x86', checked: true },
-                            { id: 'arm', title: 'ARM / M1' },
-                        ],
-                        defaultValue: DEFAULT_PLATFORM,
-                    },
-                    {
-                        id: 'package',
-                        title: 'Package manager',
-                        options: [
-                            { id: 'pip', title: 'pip', checked: true },
-                            !nightly ? { id: 'conda', title: 'conda' } : null,
-                            { id: 'source', title: 'from source' },
-                        ].filter((o) => o),
-                    },
-                    {
-                        id: 'hardware',
-                        title: 'Hardware',
-                        options: [
-                            { id: 'cpu', title: 'CPU', checked: DEFAULT_HARDWARE === 'cpu' },
-                            { id: 'gpu', title: 'GPU', checked: DEFAULT_HARDWARE == 'gpu' },
-                        ],
-                        dropdown: Object.keys(CUDA).map((id) => ({
-                            id: CUDA[id],
-                            title: `CUDA ${id}`,
-                        })),
-                        defaultValue: DEFAULT_CUDA,
-                    },
-                    {
-                        id: 'config',
-                        title: 'Configuration',
-                        multiple: true,
-                        options: [
-                            {
-                                id: 'venv',
-                                title: 'virtual env',
-                                help: 'Use a virtual environment',
-                            },
-                            {
-                                id: 'train',
-                                title: 'train models',
-                                help: 'Check this if you plan to train your own models with spaCy to install extra dependencies and data resources',
-                            },
-                        ],
-                    },
-                    {
-                        id: 'models',
-                        title: 'Trained pipelines',
-                        multiple: true,
-                        options: models
-                            .sort((a, b) => a.name.localeCompare(b.name))
-                            .map(({ code, name }) => ({
-                                id: code,
-                                title: name,
-                                checked: DEFAULT_MODELS.includes(code),
-                            })),
-                    },
-                ]
-                if (selectedModels.length) {
-                    data.push({
-                        id: 'optimize',
-                        title: 'Select pipeline for',
-                        options: [
-                            {
-                                id: 'efficiency',
-                                title: 'efficiency',
-                                checked: DEFAULT_OPT === 'efficiency',
-                                help: 'Faster and smaller pipeline, but less accurate',
-                            },
-                            {
-                                id: 'accuracy',
-                                title: 'accuracy',
-                                checked: DEFAULT_OPT === 'accuracy',
-                                help: 'Larger and slower pipeline, but more accurate',
-                            },
-                        ],
-                    })
-                }
-                return (
-                    <Quickstart
-                        data={data}
-                        title={title}
-                        id={id}
-                        setters={setters}
-                        showDropdown={showDropdown}
-                    >
-                        <QS os="mac" hardware="gpu" platform="arm">
-                            # Note M1 GPU support is experimental, see{' '}
-                            <a href="https://github.com/explosion/thinc/issues/792">
-                                Thinc issue #792
-                            </a>
-                        </QS>
-                        <QS package="pip" config="venv">
-                            python -m venv .env
-                        </QS>
-                        <QS package="pip" config="venv" os="mac">
-                            source .env/bin/activate
-                        </QS>
-                        <QS package="pip" config="venv" os="linux">
-                            source .env/bin/activate
-                        </QS>
-                        <QS package="pip" config="venv" os="windows">
-                            .env\Scripts\activate
-                        </QS>
-                        <QS package="source" config="venv">
-                            python -m venv .env
-                        </QS>
-                        <QS package="source" config="venv" os="mac">
-                            source .env/bin/activate
-                        </QS>
-                        <QS package="source" config="venv" os="linux">
-                            source .env/bin/activate
-                        </QS>
-                        <QS package="source" config="venv" os="windows">
-                            .env\Scripts\activate
-                        </QS>
-                        <QS package="conda" config="venv">
-                            conda create -n venv
-                        </QS>
-                        <QS package="conda" config="venv">
-                            conda activate venv
-                        </QS>
-                        <QS package="pip">pip install -U pip setuptools wheel</QS>
-                        <QS package="source">pip install -U pip setuptools wheel</QS>
-                        <QS package="pip">
-                            {pipExtras
-                                ? `pip install -U '${pkg}[${pipExtras}]'`
-                                : `pip install -U ${pkg}`}
-                            {nightly ? ' --pre' : ''}
-                        </QS>
-                        <QS package="conda">conda install -c conda-forge spacy</QS>
-                        <QS package="conda" hardware="gpu" os="windows">
-                            conda install -c conda-forge cupy
-                        </QS>
-                        <QS package="conda" hardware="gpu" os="linux">
-                            conda install -c conda-forge cupy
-                        </QS>
-                        <QS package="conda" hardware="gpu" os="mac" platform="x86">
-                            conda install -c conda-forge cupy
-                        </QS>
-                        <QS package="conda" config="train">
-                            conda install -c conda-forge spacy-transformers
-                        </QS>
-                        <QS package="source">
-                            git clone https://github.com/{repo}
-                            {nightly ? ` --branch ${DEFAULT_BRANCH}` : ''}
-                        </QS>
-                        <QS package="source">cd spaCy</QS>
-                        <QS package="source">pip install -r requirements.txt</QS>
-                        <QS package="source">
-                            pip install --no-build-isolation --editable{' '}
-                            {train || hardware == 'gpu' ? `'.[${pipExtras}]'` : '.'}
-                        </QS>
-                        <QS config="train" package="conda" comment prompt={false}>
-                            # packages only available via pip
-                        </QS>
-                        <QS config="train" package="conda">
-                            pip install spacy-lookups-data
-                        </QS>
 
-                        {models.map(({ code, models: modelOptions }) => {
-                            const pkg = modelOptions[efficiency ? 0 : modelOptions.length - 1]
-                            return (
-                                <QS models={code} key={code}>
-                                    python -m spacy download {pkg}
-                                </QS>
-                            )
-                        })}
-                    </Quickstart>
+    const { nightly } = siteMetadata
+    const pkg = nightly ? 'spacy-nightly' : 'spacy'
+    const languages = models.languages.filter(({ models }) => !!models)
+    const data = [
+        {
+            id: 'os',
+            title: 'Operating system',
+            options: [
+                { id: 'mac', title: 'macOS / OSX', checked: true },
+                { id: 'windows', title: 'Windows' },
+                { id: 'linux', title: 'Linux' },
+            ],
+            defaultValue: DEFAULT_OS,
+        },
+        {
+            id: 'platform',
+            title: 'Platform',
+            options: [
+                { id: 'x86', title: 'x86', checked: true },
+                { id: 'arm', title: 'ARM / M1' },
+            ],
+            defaultValue: DEFAULT_PLATFORM,
+        },
+        {
+            id: 'package',
+            title: 'Package manager',
+            options: [
+                { id: 'pip', title: 'pip', checked: true },
+                !nightly ? { id: 'conda', title: 'conda' } : null,
+                { id: 'source', title: 'from source' },
+            ].filter((o) => o),
+        },
+        {
+            id: 'hardware',
+            title: 'Hardware',
+            options: [
+                { id: 'cpu', title: 'CPU', checked: DEFAULT_HARDWARE === 'cpu' },
+                { id: 'gpu', title: 'GPU', checked: DEFAULT_HARDWARE == 'gpu' },
+            ],
+            dropdown: Object.keys(CUDA).map((id) => ({
+                id: CUDA[id],
+                title: `CUDA ${id}`,
+            })),
+            defaultValue: DEFAULT_CUDA,
+        },
+        {
+            id: 'config',
+            title: 'Configuration',
+            multiple: true,
+            options: [
+                {
+                    id: 'venv',
+                    title: 'virtual env',
+                    help: 'Use a virtual environment',
+                },
+                {
+                    id: 'train',
+                    title: 'train models',
+                    help: 'Check this if you plan to train your own models with spaCy to install extra dependencies and data resources',
+                },
+            ],
+        },
+        {
+            id: 'models',
+            title: 'Trained pipelines',
+            multiple: true,
+            options: languages
+                .sort((a, b) => a.name.localeCompare(b.name))
+                .map(({ code, name }) => ({
+                    id: code,
+                    title: name,
+                    checked: DEFAULT_MODELS.includes(code),
+                })),
+        },
+    ]
+    if (selectedModels.length) {
+        data.push({
+            id: 'optimize',
+            title: 'Select pipeline for',
+            options: [
+                {
+                    id: 'efficiency',
+                    title: 'efficiency',
+                    checked: DEFAULT_OPT === 'efficiency',
+                    help: 'Faster and smaller pipeline, but less accurate',
+                },
+                {
+                    id: 'accuracy',
+                    title: 'accuracy',
+                    checked: DEFAULT_OPT === 'accuracy',
+                    help: 'Larger and slower pipeline, but more accurate',
+                },
+            ],
+        })
+    }
+    return (
+        <Quickstart data={data} title={title} id={id} setters={setters} showDropdown={showDropdown}>
+            <QS os="mac" hardware="gpu" platform="arm">
+                # Note M1 GPU support is experimental, see{' '}
+                <a href="https://github.com/explosion/thinc/issues/792">Thinc issue #792</a>
+            </QS>
+            <QS package="pip" config="venv">
+                python -m venv .env
+            </QS>
+            <QS package="pip" config="venv" os="mac">
+                source .env/bin/activate
+            </QS>
+            <QS package="pip" config="venv" os="linux">
+                source .env/bin/activate
+            </QS>
+            <QS package="pip" config="venv" os="windows">
+                .env\Scripts\activate
+            </QS>
+            <QS package="source" config="venv">
+                python -m venv .env
+            </QS>
+            <QS package="source" config="venv" os="mac">
+                source .env/bin/activate
+            </QS>
+            <QS package="source" config="venv" os="linux">
+                source .env/bin/activate
+            </QS>
+            <QS package="source" config="venv" os="windows">
+                .env\Scripts\activate
+            </QS>
+            <QS package="conda" config="venv">
+                conda create -n venv
+            </QS>
+            <QS package="conda" config="venv">
+                conda activate venv
+            </QS>
+            <QS package="pip">pip install -U pip setuptools wheel</QS>
+            <QS package="source">pip install -U pip setuptools wheel</QS>
+            <QS package="pip">
+                {pipExtras ? `pip install -U '${pkg}[${pipExtras}]'` : `pip install -U ${pkg}`}
+                {nightly ? ' --pre' : ''}
+            </QS>
+            <QS package="conda">conda install -c conda-forge spacy</QS>
+            <QS package="conda" hardware="gpu">
+                conda install -c conda-forge cupy
+            </QS>
+            <QS package="conda" config="train">
+                conda install -c conda-forge spacy-transformers
+            </QS>
+            <QS package="source">
+                git clone https://github.com/{repo}
+                {nightly ? ` --branch ${DEFAULT_BRANCH}` : ''}
+            </QS>
+            <QS package="source">cd spaCy</QS>
+            <QS package="source">pip install -r requirements.txt</QS>
+            <QS package="source">
+                pip install --no-build-isolation --editable{' '}
+                {train || hardware == 'gpu' ? `'.[${pipExtras}]'` : '.'}
+            </QS>
+            <QS config="train" package="conda" comment prompt={false}>
+                # packages only available via pip
+            </QS>
+            <QS config="train" package="conda">
+                pip install spacy-lookups-data
+            </QS>
+
+            {languages.map(({ code, models: modelOptions }) => {
+                const pkg = modelOptions[efficiency ? 0 : modelOptions.length - 1]
+                return (
+                    <QS models={code} key={code}>
+                        python -m spacy download {pkg}
+                    </QS>
                 )
-            }}
-        />
+            })}
+        </Quickstart>
     )
 }
 
 export default QuickstartInstall
-
-const query = graphql`
-    query QuickstartInstallQuery {
-        site {
-            siteMetadata {
-                nightly
-                languages {
-                    code
-                    name
-                    models
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/widgets/quickstart-models.js b/website/src/widgets/quickstart-models.js
index af44788b5..b2a0a6280 100644
--- a/website/src/widgets/quickstart-models.js
+++ b/website/src/widgets/quickstart-models.js
@@ -1,7 +1,7 @@
 import React, { Fragment, useState } from 'react'
-import { StaticQuery, graphql } from 'gatsby'
 
 import { Quickstart, QS } from '../components/quickstart'
+import models from '../../meta/languages.json'
 
 const DEFAULT_LANG = 'en'
 const DEFAULT_OPT = 'efficiency'
@@ -62,80 +62,59 @@ const QuickstartInstall = ({ id, title, description, children }) => {
         lang: setLang,
         optimize: (v) => setEfficiency(v.includes('efficiency')),
     }
-    return (
-        <StaticQuery
-            query={query}
-            render={({ site }) => {
-                const models = site.siteMetadata.languages.filter(({ models }) => models !== null)
-                data[0].dropdown = models
-                    .sort((a, b) => a.name.localeCompare(b.name))
-                    .map(({ code, name }) => ({
-                        id: code,
-                        title: name,
-                    }))
-                return (
-                    <Quickstart
-                        data={data}
-                        title={title}
-                        id={id}
-                        description={description}
-                        setters={setters}
-                        copy={false}
-                    >
-                        {models.map(({ code, models, example }) => {
-                            const pkg = efficiency ? models[0] : models[models.length - 1]
-                            const exampleText = example || 'No text available yet'
-                            return lang !== code ? null : (
-                                <Fragment key={code}>
-                                    <QS>python -m spacy download {pkg}</QS>
-                                    <QS divider />
-                                    <QS load="spacy" prompt="python">
-                                        import spacy
-                                    </QS>
-                                    <QS load="spacy" prompt="python">
-                                        nlp = spacy.load("{pkg}")
-                                    </QS>
-                                    <QS load="module" prompt="python">
-                                        import {pkg}
-                                    </QS>
-                                    <QS load="module" prompt="python">
-                                        nlp = {pkg}.load()
-                                    </QS>
-                                    <QS config="example" prompt="python">
-                                        doc = nlp("{exampleText}")
-                                    </QS>
-                                    <QS config="example" prompt="python">
-                                        print([
-                                        {code === 'xx'
-                                            ? '(ent.text, ent.label) for ent in doc.ents'
-                                            : '(w.text, w.pos_) for w in doc'}
-                                        ])
-                                    </QS>
-                                </Fragment>
-                            )
-                        })}
 
-                        {children}
-                    </Quickstart>
+    const languages = models.languages.filter(({ models }) => !!models)
+    data[0].dropdown = languages
+        .sort((a, b) => a.name.localeCompare(b.name))
+        .map(({ code, name }) => ({
+            id: code,
+            title: name,
+        }))
+    return (
+        <Quickstart
+            data={data}
+            title={title}
+            id={id}
+            description={description}
+            setters={setters}
+            copy={false}
+        >
+            {languages.map(({ code, models, example }) => {
+                const pkg = efficiency ? models[0] : models[models.length - 1]
+                const exampleText = example || 'No text available yet'
+                return lang !== code ? null : (
+                    <Fragment key={code}>
+                        <QS>python -m spacy download {pkg}</QS>
+                        <QS divider />
+                        <QS load="spacy" prompt="python">
+                            import spacy
+                        </QS>
+                        <QS load="spacy" prompt="python">
+                            nlp = spacy.load(&quot;{pkg}&quot;)
+                        </QS>
+                        <QS load="module" prompt="python">
+                            import {pkg}
+                        </QS>
+                        <QS load="module" prompt="python">
+                            nlp = {pkg}.load()
+                        </QS>
+                        <QS config="example" prompt="python">
+                            doc = nlp(&quot;{exampleText}&quot;)
+                        </QS>
+                        <QS config="example" prompt="python">
+                            print([
+                            {code === 'xx'
+                                ? '(ent.text, ent.label) for ent in doc.ents'
+                                : '(w.text, w.pos_) for w in doc'}
+                            ])
+                        </QS>
+                    </Fragment>
                 )
-            }}
-        />
+            })}
+
+            {children}
+        </Quickstart>
     )
 }
 
 export default QuickstartInstall
-
-const query = graphql`
-    query QuickstartModelsQuery {
-        site {
-            siteMetadata {
-                languages {
-                    code
-                    name
-                    models
-                    example
-                }
-            }
-        }
-    }
-`
diff --git a/website/src/widgets/quickstart-training.js b/website/src/widgets/quickstart-training.js
index 4c6051875..68971d2d9 100644
--- a/website/src/widgets/quickstart-training.js
+++ b/website/src/widgets/quickstart-training.js
@@ -1,10 +1,10 @@
 import React, { useState } from 'react'
-import { StaticQuery, graphql } from 'gatsby'
 import highlightCode from 'gatsby-remark-prismjs/highlight-code.js'
 
 import { Quickstart } from '../components/quickstart'
 import generator, { DATA as GENERATOR_DATA } from './quickstart-training-generator'
 import { htmlToReact } from '../components/util'
+import models from '../../meta/languages.json'
 
 const DEFAULT_LANG = 'en'
 const DEFAULT_HARDWARE = 'cpu'
@@ -112,54 +112,31 @@ export default function QuickstartTraining({ id, title, download = 'base_config.
         .split('\n')
         .map((line) => (line.startsWith('#') ? `<span class="token comment">${line}</span>` : line))
         .join('\n')
+
+    let data = DATA
+    data[0].dropdown = models.languages
+        .map(({ name, code }) => ({
+            id: code,
+            title: name,
+        }))
+        .sort((a, b) => a.title.localeCompare(b.title))
+    if (!_components.includes('textcat')) {
+        data = data.map((field) => (field.id === 'textcat' ? { ...field, hidden: true } : field))
+    }
     return (
-        <StaticQuery
-            query={query}
-            render={({ site }) => {
-                let data = DATA
-                const langs = site.siteMetadata.languages
-                data[0].dropdown = langs
-                    .map(({ name, code }) => ({
-                        id: code,
-                        title: name,
-                    }))
-                    .sort((a, b) => a.title.localeCompare(b.title))
-                if (!_components.includes('textcat')) {
-                    data = data.map((field) =>
-                        field.id === 'textcat' ? { ...field, hidden: true } : field
-                    )
-                }
-                return (
-                    <Quickstart
-                        id="quickstart-widget"
-                        Container="div"
-                        download={download}
-                        rawContent={rawContent}
-                        data={data}
-                        title={title}
-                        id={id}
-                        setters={setters}
-                        hidePrompts
-                        small
-                        codeLang="ini"
-                    >
-                        {htmlToReact(displayContent)}
-                    </Quickstart>
-                )
-            }}
-        />
+        <Quickstart
+            Container="div"
+            download={download}
+            rawContent={rawContent}
+            data={data}
+            title={title}
+            id={id}
+            setters={setters}
+            hidePrompts
+            small
+            codeLang="ini"
+        >
+            {htmlToReact(displayContent)}
+        </Quickstart>
     )
 }
-
-const query = graphql`
-    query QuickstartTrainingQuery {
-        site {
-            siteMetadata {
-                languages {
-                    code
-                    name
-                }
-            }
-        }
-    }
-`