Skip to content

Conversation

@m-abdelwahab
Copy link
Contributor

Current

CleanShot 2026-02-04 at 04 12 49@2x

New

CleanShot 2026-02-04 at 04 13 05@2x

This PR contains a comprehensive revamp of the Railway docs

New Information Architecture

  • Restructured from a flat src/docs/ layout to a hierarchical content/docs/ structure organized by topic
  • Created dedicated sections: /access, /ai, /builds, /cli, /config-as-code, /cron-jobs, /databases, /deployments, /enterprise, /environments, /functions, /integrations, /networking, /observability, /platform, /pricing, /projects, /services, /storage-buckets, /templates, /troubleshooting, /variables, /volumes
  • Separated tutorials and framework guides into content/guides/
  • Added landing pages for each major section to improve navigation
  • Merged related guide/reference pages (e.g., healthchecks guide + reference → single /deployments/healthchecks)
  • Improved sidebar navigation

New content + style guide

  • CLI docs where all components are documented
  • Agent Skills docs
  • Document MFA
  • Simpler docs home with quick links and clear link to Railway
  • Use Sentence case for all headings
  • Significantly reduce the usage of our, we, let's
  • Add contributing guide

Design improvements

  • Match Railway's brand colors
  • Better typography
  • New UI components: steps component, code block, table of contents, inline table of contents, more page actions, better code blocks (supports line highlighting, showing diffs, tabs, file icons), image zoom
  • Icons from hugeicons.com
  • Theme adapts based on the system theme of force dark/light mode

Technical Changes

  • refactor all components to use kebab-case
  • Content System: Migrated from Contentlayer to content-collections
  • Styling: Removed twin.macro/style-components in favor of Tailwind v4
  • Added local Meilisearch search setup for development
  • Generate an icon spritesheet for performant svg icons

- Removed the hast package from dependencies.
- Added @types/hast and @types/react-dom to devDependencies for improved type support.
- Updated versions of @types/react-dom and related packages in pnpm-lock.yaml for consistency.
…omponent

- Introduced a new optional `title` prop in the Link component for enhanced accessibility.
- Modified the `breadcrumbs` type in the SEO component to allow optional `url` properties for improved flexibility.
- Cleaned up formatting in the SEO component for better readability.
- Removed outdated reference to `next/navigation-types/compat/navigation`.
- Updated the link for TypeScript configuration documentation to the correct page.
- Introduced a `shouldWrapInFrame` function to determine if images should be wrapped in a Frame for zooming, excluding certain patterns.
- Updated the Image component to conditionally wrap images in a Frame based on the new logic.
- Added support for standard markdown images by mapping the `img` tag to the Image component in relevant pages.
…umentation

- Updated .env.example to include Meilisearch configuration for local development.
- Modified docker-compose.yml to adjust the scraping command for local indexing.
- Enhanced meilisearch-docs-scraper.config.json with stop URLs to refine scraping.
- Updated README.md to include prerequisites for enabling local search functionality.
- Improved sidebar component styling for better visual indication of the current section.
Manually port commits ef9c174 and b56bc98 from main:

- Add "Last updated" date to docs/guides pages using git history
- Fix API token header: Team-Access-Token → Authorization: Bearer
- Migrate team → workspace terminology across documentation
- Rename teams.md → workspaces.md and update all internal links
Resolved conflicts by keeping docs revamp structure:
- Removed files that were reorganized/deleted in the revamp
- Kept new file locations (e.g., content/docs/projects/workspaces.md)
@railway-app
Copy link

railway-app bot commented Feb 4, 2026

🚅 Deployed to the docs-pr-1042 environment in 🪄 *.railway.com

Service Status Web Updated (UTC)
Docs Frontend ✅ Success (View Logs) Web Feb 4, 2026 at 3:32 pm
5 services not affected by this PR
  • devicons
  • OG
  • Geofeed
  • Turnout
  • Blog

Comment on lines +78 to +81
const searchSessionId = useMemo(() => {
if (typeof window === "undefined") return "";
return `search_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}, []);

Check failure

Code scanning / CodeQL

Insecure randomness High

This uses a cryptographically insecure random number generated at
Math.random()
in a security context.

Copilot Autofix

AI about 14 hours ago

General fix: replace the use of Math.random() with a cryptographically secure random source when generating identifiers that are treated as session IDs. In browsers, this means using crypto.getRandomValues to obtain random bytes and encoding them as a string.

Best way to fix this code without changing existing functionality: keep the same search_<timestamp>_<random> format but implement a small helper function that uses window.crypto.getRandomValues to generate a random string instead of Math.random().toString(36).substring(2, 9). We can scope this helper inside useSearchAnalytics (or above it) so we don’t affect other files. If window.crypto is not available for some reason, we can conservatively fall back to Math.random() to preserve behavior, but the primary path will be cryptographically secure in browsers where crypto exists, which is the normal case for React apps using posthog-js.

Concrete changes in src/hooks/use-search-analytics.ts:

  1. Add a small helper function (e.g., generateSecureRandomString) to generate a base-36 string of length 7 using window.crypto.getRandomValues(new Uint32Array(1)).
  2. Update the useMemo that creates searchSessionId so that:
    • It checks for window.crypto and uses generateSecureRandomString() when available.
    • Optionally falls back to the existing Math.random() behavior if crypto is not present, avoiding runtime errors in edge environments.
  3. Keep the existing timestamp prefix and ID shape (search_${Date.now()}_<random>), so all existing downstream usages and analytics dashboards remain compatible.

No new imports are needed, since window.crypto is part of the web platform.

Suggested changeset 1
src/hooks/use-search-analytics.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/hooks/use-search-analytics.ts b/src/hooks/use-search-analytics.ts
--- a/src/hooks/use-search-analytics.ts
+++ b/src/hooks/use-search-analytics.ts
@@ -68,6 +68,21 @@
 }
 
 /**
+ * Generate a cryptographically secure random string.
+ * Falls back to Math.random if crypto is unavailable.
+ */
+function generateSecureRandomString(length: number): string {
+  if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues) {
+    const array = new Uint32Array(1);
+    window.crypto.getRandomValues(array);
+    return array[0].toString(36).substring(2, 2 + length);
+  }
+
+  // Fallback for environments without crypto; preserves previous behavior pattern.
+  return Math.random().toString(36).substring(2, 2 + length);
+}
+
+/**
  * Hook for tracking search analytics in PostHog
  */
 export function useSearchAnalytics() {
@@ -77,7 +92,8 @@
   // Generate a unique search session ID for correlating events
   const searchSessionId = useMemo(() => {
     if (typeof window === "undefined") return "";
-    return `search_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
+    const randomPart = generateSecureRandomString(7);
+    return `search_${Date.now()}_${randomPart}`;
   }, []);
 
   /**
EOF
@@ -68,6 +68,21 @@
}

/**
* Generate a cryptographically secure random string.
* Falls back to Math.random if crypto is unavailable.
*/
function generateSecureRandomString(length: number): string {
if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues) {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return array[0].toString(36).substring(2, 2 + length);
}

// Fallback for environments without crypto; preserves previous behavior pattern.
return Math.random().toString(36).substring(2, 2 + length);
}

/**
* Hook for tracking search analytics in PostHog
*/
export function useSearchAnalytics() {
@@ -77,7 +92,8 @@
// Generate a unique search session ID for correlating events
const searchSessionId = useMemo(() => {
if (typeof window === "undefined") return "";
return `search_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
const randomPart = generateSecureRandomString(7);
return `search_${Date.now()}_${randomPart}`;
}, []);

/**
Copilot is powered by AI and may make mistakes. Always verify output.
current = current[key] as Record<string, unknown>;
}

current[keys[keys.length - 1]] = value;

Check warning

Code scanning / CodeQL

Prototype-polluting function Medium

The property chain
here
is recursively assigned to
current
without guarding against prototype pollution.

Copilot Autofix

AI about 14 hours ago

In general, to fix deep-assignment/prototype-pollution issues, you must prevent dangerous property names (__proto__, constructor, and optionally prototype) from being traversed or assigned, or require that you only recurse into/assign existing own properties on a known-safe object. Here, the minimal and safest fix is to block those special keys when iterating through keys and when setting the final property.

Best single fix without changing existing functionality for valid inputs:

  • Add a small helper isSafeKey(key: string): boolean that returns false for __proto__, constructor, and prototype, and true otherwise.
  • In the for loop at lines 385–390, skip any segment that is not a safe key so we never rebind current to Object.prototype or its constructor.
  • Before the final assignment at line 393, check that the last key is safe; if not, return without doing anything.
  • The rest of the behavior (creating intermediate objects when missing / not objects, and assigning value at the leaf) remains unchanged for all non-dangerous keys.

All needed changes are contained within src/components/graphql-code-tabs.tsx in the region around setNestedValue. No additional imports or external libraries are required.

Suggested changeset 1
src/components/graphql-code-tabs.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/components/graphql-code-tabs.tsx b/src/components/graphql-code-tabs.tsx
--- a/src/components/graphql-code-tabs.tsx
+++ b/src/components/graphql-code-tabs.tsx
@@ -384,15 +384,29 @@
 
   for (let i = 0; i < keys.length - 1; i++) {
     const key = keys[i];
+    // Guard against prototype pollution through special keys
+    if (!isSafeKey(key)) {
+      return;
+    }
     if (!(key in current) || typeof current[key] !== "object") {
       current[key] = {};
     }
     current = current[key] as Record<string, unknown>;
   }
 
-  current[keys[keys.length - 1]] = value;
+  const lastKey = keys[keys.length - 1];
+  // Guard against prototype pollution on final assignment
+  if (!isSafeKey(lastKey)) {
+    return;
+  }
+
+  current[lastKey] = value;
 }
 
+function isSafeKey(key: string): boolean {
+  return key !== "__proto__" && key !== "constructor" && key !== "prototype";
+}
+
 // Helper: Get a nested value using dot notation path
 function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
   const keys = path.split(".");
EOF
@@ -384,15 +384,29 @@

for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
// Guard against prototype pollution through special keys
if (!isSafeKey(key)) {
return;
}
if (!(key in current) || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key] as Record<string, unknown>;
}

current[keys[keys.length - 1]] = value;
const lastKey = keys[keys.length - 1];
// Guard against prototype pollution on final assignment
if (!isSafeKey(lastKey)) {
return;
}

current[lastKey] = value;
}

function isSafeKey(key: string): boolean {
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
}

// Helper: Get a nested value using dot notation path
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
const keys = path.split(".");
Copilot is powered by AI and may make mistakes. Always verify output.
@railway-app railway-app bot temporarily deployed to *.railway.com / docs-pr-1042 February 4, 2026 02:21 Destroyed
@railway-app railway-app bot temporarily deployed to *.railway.com / docs-pr-1042 February 4, 2026 04:09 Destroyed
@railway-app railway-app bot temporarily deployed to *.railway.com / docs-pr-1042 February 4, 2026 04:18 Destroyed
Rename url to baseUrl for clarity and fix breadcrumb schema URLs
to include the full base URL prefix for proper structured data.
- Add logo, search button, and theme switcher to TopNav
- Make top nav always visible with sticky positioning and backdrop blur
- Remove header section from sidebar (logo, badge, theme switcher, search)
- Update sidebar positioning to account for sticky top nav height
- Update page layout to accommodate new structure
- Add support for sidebar sections without titles
- Update active state styling for consistency
- Update sidebar item active state styling with background highlight
- Update parent highlighted state to use font-medium
- Add Quick Start to first sidebar section and remove Overview title
- Fix TOC IntersectionObserver rootMargin for sticky top nav height
- Replace text-based list with card grid (Quickstart, AI, CLI, Templates)
- Add gradient backgrounds with light/dark mode variants for each card
- Redesign footer with social icons layout
- Remove unused A helper component
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants