diff --git a/.lintstagedrc.js b/.lintstagedrc.js
index 717b7424a3c85..74436ca10182c 100644
--- a/.lintstagedrc.js
+++ b/.lintstagedrc.js
@@ -1,7 +1,7 @@
module.exports = {
// Lint & Prettify TS and JS files - only the staged files
"**/*.(ts|tsx|js|jsx)": (filenames) => [
- `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`,
+ `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 --no-warn-ignored ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`,
`npx prettier --write ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`,
],
diff --git a/Makefile b/Makefile
index bc86a2e26bc8b..8a821a3c1da79 100644
--- a/Makefile
+++ b/Makefile
@@ -19,27 +19,43 @@ setup:
npm install --legacy-peer-deps
# "make site" - The default lightweight build keeps the dev server fast by skipping heavy collections.
-## Run a partial build of layer5.io on your local machine.
+## Run a lightweight dev server (core profile: excludes blog, events, integrations, members, news, resources).
site:
- @echo "🏗️ Building lightweight site version (core profile excludes Members, Integrations, Blog, News, Events, and Resources collections)..."
- @echo " Use LITE_BUILD_PROFILE=content make site to include content collections while still skipping the heaviest routes."
+ @echo "🏗️ Starting dev server — core profile (fastest startup)"
+ @echo " Excluded: blog, events, integrations, members, news, resources"
+ @echo " → To include blog/news/events/resources: make site-content"
+ @echo " → To include all collections: make site-full"
+ @echo " → See all profiles: make profiles"
@npm run develop:lite
+## Run a content dev server (content profile: includes blog, news, events, resources; skips members and integrations).
+site-content:
+ @echo "🏗️ Starting dev server — content profile"
+ @echo " Excluded: integrations, members"
+ @echo " → For the lightest build: make site"
+ @echo " → To include all collections: make site-full"
+ @npm run develop:content
+
# "make site-full" forces the dev server to include every collection.
-## Run a full build of layer5.io on your local machine.
+## Run a full dev server (all collections; slowest startup — includes members and integrations).
site-full:
- @echo "🏗️ Building full site version (including Members and Integrations collections)..."
+ @echo "🏗️ Starting dev server — full profile (all collections)"
+ @echo " → For a faster dev server: make site"
@npm run develop
-## Run layer5.io on your local machine. Alternate method.
+## Run dev server directly via gatsby CLI, bypassing env-cmd (skips .env.development; Linux/macOS only).
site-fast:
BUILD_FULL_SITE=false LITE_BUILD_PROFILE=core GATSBY_CPU_COUNT=4 SHARP_CONCURRENCY=4 UV_THREADPOOL_SIZE=4 NODE_OPTIONS=--max-old-space-size=8192 gatsby develop
-## Build layer5.io on your local machine.
+## Build layer5.io for production.
build:
npm run build
-## Empty build cache and rebuild layer5.io on your local machine (developer use only; CI uses `npm run build` directly).
+## Clear Gatsby build cache without rebuilding (use before switching profiles or after dependency changes).
+cache-clean:
+ npm run clean
+
+## Clear Gatsby build cache and run a full production build (slow; use make cache-clean to skip the rebuild).
clean:
npm run clean && make build
@@ -47,19 +63,38 @@ clean:
lint:
npm run lint
-## Kill process running the site
+## Kill process running the site.
kill:
lsof -ti:8000 | xargs kill -9 2>/dev/null || true
## Prepare a list of features for the pricing page.
-features:
+features:
curl -L https://docs.google.com/spreadsheets/d/e/2PACX-1vQwzrUSKfuSRcpkp7sJTw1cSB63s4HCjYLJeGPWECsvqn222hjaaONQlN4X8auKvlaB0es3BqV5rQyz/pub\?gid\=1153419764\&single\=true\&output\=csv -o .github/build/spreadsheet.csv
node .github/build/features-to-json.js .github/build/spreadsheet.csv src/sections/Pricing/feature_data.json
rm .github/build/spreadsheet.csv
-.PHONY: setup build site site-full clean site-fast lint features
+## List available build profiles and the collections each excludes.
+profiles:
+ @echo "Build profiles:"
+ @echo ""
+ @echo " core (make site) — excludes: blog, events, integrations, members, news, resources"
+ @echo " content (make site-content) — excludes: integrations, members"
+ @echo " full (make site-full) — excludes: nothing (all collections included)"
+ @echo " none (make site-custom) — excludes: only what BUILD_COLLECTIONS_EXCLUDE specifies"
+ @echo ""
+ @echo "À la carte (comma-separated collection names to exclude):"
+ @echo " BUILD_COLLECTIONS_EXCLUDE=members,events make site-custom"
-## Analyze webpack bundle with FCP optimization
+## Run dev server with only the collections you specify (set BUILD_COLLECTIONS_EXCLUDE to exclude by name).
+site-custom:
+ @echo "🏗️ Starting dev server — custom profile (none base + explicit exclusions)"
+ @echo " Excluded: $${BUILD_COLLECTIONS_EXCLUDE:-none}"
+ @echo " Example: BUILD_COLLECTIONS_EXCLUDE=members,events make site-custom"
+ @npm run develop:custom
+
+## Analyze webpack bundle composition (opens browser with interactive bundle breakdown).
site-analyze:
@echo "🏗️ Building site with webpack bundle analyzer..."
ANALYZE_BUNDLE=true npm run build
+
+.PHONY: setup build site site-content site-full site-fast site-custom cache-clean clean lint kill features profiles site-analyze
diff --git a/gatsby-node.js b/gatsby-node.js
index 356b23dd86e25..f5eeb362a9340 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -635,6 +635,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "Member profiles disabled in lite mode",
description:
"The members collection is intentionally skipped when BUILD_FULL_SITE=false to keep local builds fast.",
+ enabledBy: "full",
},
},
{
@@ -646,6 +647,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "Integrations disabled in lite mode",
description:
"Integrations are heavy to source, so this route shows a placeholder during lightweight builds.",
+ enabledBy: "full",
},
},
{
@@ -657,6 +659,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "Blog posts disabled in lite mode",
description:
"The default lightweight build skips the blog collection to keep local builds responsive.",
+ enabledBy: "content",
},
},
{
@@ -668,6 +671,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "News posts disabled in lite mode",
description:
"The default lightweight build skips the news collection to reduce local memory consumption.",
+ enabledBy: "content",
},
},
{
@@ -679,6 +683,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "Resources disabled in lite mode",
description:
"The default lightweight build skips the resources collection to reduce local memory consumption.",
+ enabledBy: "content",
},
},
{
@@ -690,6 +695,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
heading: "Events disabled in lite mode",
description:
"The default lightweight build skips the events collection to keep local builds responsive.",
+ enabledBy: "content",
},
},
];
@@ -700,7 +706,7 @@ exports.createPages = async ({ actions, graphql, reporter }) => {
envCreatePage({
path: page.path,
matchPath: page.matchPath,
- context: page.context,
+ context: { ...page.context, collection: page.collection },
component: LitePlaceholderTemplate,
}),
);
diff --git a/package.json b/package.json
index fbcf9d651896b..e148d256b555d 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,11 @@
"clean:all": "gatsby clean && rimraf node_modules",
"develop": "cross-env BUILD_FULL_SITE=true GATSBY_CPU_COUNT=4 SHARP_CONCURRENCY=4 UV_THREADPOOL_SIZE=4 NODE_OPTIONS=--max-old-space-size=8192 env-cmd -f .env.development gatsby develop",
"develop:lite": "cross-env BUILD_FULL_SITE=false LITE_BUILD_PROFILE=core GATSBY_CPU_COUNT=4 SHARP_CONCURRENCY=4 UV_THREADPOOL_SIZE=4 NODE_OPTIONS=--max-old-space-size=8192 env-cmd -f .env.development gatsby develop",
+ "develop:content": "cross-env BUILD_FULL_SITE=false LITE_BUILD_PROFILE=content GATSBY_CPU_COUNT=4 SHARP_CONCURRENCY=4 UV_THREADPOOL_SIZE=4 NODE_OPTIONS=--max-old-space-size=8192 env-cmd -f .env.development gatsby develop",
+ "develop:custom": "cross-env BUILD_FULL_SITE=false LITE_BUILD_PROFILE=none GATSBY_CPU_COUNT=4 SHARP_CONCURRENCY=4 UV_THREADPOOL_SIZE=4 NODE_OPTIONS=--max-old-space-size=8192 env-cmd -f .env.development gatsby develop",
"dev": "npm run develop:lite",
"start": "npm run develop:lite",
+ "start:content": "npm run develop:content",
"start:full": "npm run develop",
"serve": "gatsby serve",
"lint": "eslint --fix .",
diff --git a/src/templates/lite-placeholder.js b/src/templates/lite-placeholder.js
index 8c908e42ce96d..c5f55ae24cee6 100644
--- a/src/templates/lite-placeholder.js
+++ b/src/templates/lite-placeholder.js
@@ -1,55 +1,351 @@
-import React from "react";
+import React, { useState, useCallback, useEffect, useRef } from "react";
import SEO from "../components/seo";
+const BRAND_COLOR = "#00b39f";
+
+const ALL_COLLECTIONS = [
+ "blog",
+ "events",
+ "integrations",
+ "members",
+ "news",
+ "resources",
+];
+
+// Which profile is the minimum required to enable each collection.
+const COLLECTION_WEIGHT = {
+ blog: "content",
+ events: "content",
+ news: "content",
+ resources: "content",
+ integrations: "full",
+ members: "full",
+};
+
+// Quick-restore commands shown above the picker, keyed by enabledBy value.
+const RESTORE_COMMANDS = {
+ content: [
+ {
+ cmd: "make site-content",
+ note: "includes blog, news, events, resources — skips members and integrations",
+ },
+ { cmd: "make site-full", note: "includes all collections" },
+ ],
+ full: [{ cmd: "make site-full", note: "includes all collections" }],
+};
+
+const CONTENT_EXCLUSIONS = new Set(["integrations", "members"]);
+
+// Derive the right command from the set of collections the user wants to include.
+function generateCommand(included) {
+ const excluded = ALL_COLLECTIONS.filter((c) => !included.has(c)).sort();
+
+ if (excluded.length === 0) return "make site-full";
+ if (excluded.length === ALL_COLLECTIONS.length) return "make site";
+
+ const isContentProfile =
+ excluded.length === CONTENT_EXCLUSIONS.size &&
+ excluded.every((c) => CONTENT_EXCLUSIONS.has(c));
+ if (isContentProfile) return "make site-content";
+
+ return `BUILD_COLLECTIONS_EXCLUDE=${excluded.join(",")} make site-custom`;
+}
+
+// ── Copy button ────────────────────────────────────────────────────────────────
+
+function CopyButton({ text }) {
+ const [copied, setCopied] = useState(false);
+ const timerRef = useRef(null);
+
+ useEffect(() => {
+ return () => {
+ if (timerRef.current) clearTimeout(timerRef.current);
+ };
+ }, []);
+
+ const handleCopy = useCallback(() => {
+ if (
+ typeof navigator === "undefined" ||
+ typeof window === "undefined" ||
+ !window.isSecureContext ||
+ !navigator.clipboard?.writeText
+ ) {
+ return;
+ }
+
+ navigator.clipboard
+ .writeText(text)
+ .then(() => {
+ setCopied(true);
+ if (timerRef.current) clearTimeout(timerRef.current);
+ timerRef.current = setTimeout(() => setCopied(false), 1500);
+ })
+ .catch(() => {});
+ }, [text]);
+
+ return (
+ <>
+
+
+ {copied ? "Copied!" : ""}
+
+ >
+ );
+}
+
+// ── Single command row with optional note ──────────────────────────────────────
+
+function CommandLine({ cmd, note }) {
+ return (
+
+
+
+ {cmd}
+
+
+
+ {note && (
+
+ {note}
+
+ )}
+
+ );
+}
+
+// ── Main template ──────────────────────────────────────────────────────────────
+
const LitePlaceholder = ({ pageContext, location }) => {
const {
heading = "Content disabled in lite mode",
description = "This route is intentionally skipped when BUILD_FULL_SITE=false.",
+ enabledBy = "full",
+ collection,
} = pageContext;
- const instructions =
- "Run `make site-full` (or set BUILD_FULL_SITE=true) to source the full dataset, then reload this path.";
+ const restoreCommands = RESTORE_COMMANDS[enabledBy] ?? RESTORE_COMMANDS.full;
+
+ // Pre-check the current route's collection so the generated command is immediately useful.
+ const [included, setIncluded] = useState(() => {
+ const initial = new Set();
+ if (collection && ALL_COLLECTIONS.includes(collection))
+ initial.add(collection);
+ return initial;
+ });
+
+ const toggle = useCallback((name) => {
+ setIncluded((prev) => {
+ const next = new Set(prev);
+ next.has(name) ? next.delete(name) : next.add(name);
+ return next;
+ });
+ }, []);
+
+ const customCommand = generateCommand(included);
return (
- <>
-
-
+
-
- {heading}
+ {heading}
+
+
+ {description}
+
+
+ {/* ── Quick restore ─────────────────────────────────────────────────── */}
+
+
+ Restart your dev server with one of these commands to restore this
+ route:
-
{description}
-
- {instructions}
+ {restoreCommands.map(({ cmd, note }) => (
+
+ ))}
+
+
+ {/* ── À la carte picker ─────────────────────────────────────────────── */}
+
+
+ Build collections à la carte
- {location?.pathname && (
-
- Requested path: {location.pathname}
-
- )}
-
- >
+
+ Check the collections you want included, then copy the generated
+ command.
+
+
+ {ALL_COLLECTIONS.map((name) => (
+
+ ))}
+
+
+
+
+ {customCommand.startsWith("BUILD_COLLECTIONS_EXCLUDE") && (
+
+ Uses make site-custom which starts with no preset
+ exclusions, then applies only what you specify.
+
+ )}
+
+
+
+ {/* ── Contributing guide link ───────────────────────────────────────── */}
+
+ All build profiles and environment variables are documented in the{" "}
+
+ contributing guide
+
+ .
+
+
+ {location?.pathname && (
+
+ Requested path: {location.pathname}
+
+ )}
+
);
};
export default LitePlaceholder;
export const Head = ({ pageContext }) => {
- const { heading = "Content disabled in lite mode", description = "" } =
- pageContext;
- const instructions =
- "Run make site-full or set BUILD_FULL_SITE=true to include heavy collections in development.";
-
+ const {
+ heading = "Content disabled in lite mode",
+ description = "",
+ enabledBy = "full",
+ } = pageContext;
+ const commands = RESTORE_COMMANDS[enabledBy] ?? RESTORE_COMMANDS.full;
return (
);
};
diff --git a/src/utils/build-collections.js b/src/utils/build-collections.js
index 54bf6b780cbda..1dccee66480bf 100644
--- a/src/utils/build-collections.js
+++ b/src/utils/build-collections.js
@@ -1,6 +1,7 @@
const DEFAULT_LITE_BUILD_PROFILE = "core";
const LITE_BUILD_PROFILES = Object.freeze({
+ none: [],
content: ["members", "integrations"],
core: ["members", "integrations", "blog", "news", "events", "resources"],
});
@@ -16,7 +17,8 @@ const parseCsv = (value = "") =>
const getExcludedCollections = ({
isFullSiteBuild: shouldBuildFullSite = isFullSiteBuild(),
- liteBuildProfile = process.env.LITE_BUILD_PROFILE || DEFAULT_LITE_BUILD_PROFILE,
+ liteBuildProfile = process.env.LITE_BUILD_PROFILE ||
+ DEFAULT_LITE_BUILD_PROFILE,
buildCollectionsExclude = process.env.BUILD_COLLECTIONS_EXCLUDE,
} = {}) => {
if (shouldBuildFullSite) {
@@ -37,4 +39,4 @@ module.exports = {
LITE_BUILD_PROFILES,
getExcludedCollections,
isFullSiteBuild,
-};
\ No newline at end of file
+};