CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-reach--router

Next generation React routing library with accessible navigation and focus management.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

server-rendering.mddocs/

Server-Side Rendering

Components and utilities for rendering routes on the server with static location context.

Capabilities

ServerLocation Component

Location provider component designed for server-side rendering that provides static location context.

/**
 * Location provider for server-side rendering with static location context
 * @param props - Server location configuration
 */
function ServerLocation(props: {
  url: string;
  children: React.ReactNode;
}): React.ReactElement;

Props:

  • url (string, required) - The URL to use as the current location
  • children (React.ReactNode) - Child components that will receive location context

Usage Examples:

import React from "react";
import { renderToString } from "react-dom/server";
import { ServerLocation, Router } from "@reach/router";

// Basic server-side rendering
const renderApp = (url) => {
  const appHtml = renderToString(
    <ServerLocation url={url}>
      <Router>
        <Home path="/" />
        <About path="/about" />
        <Contact path="/contact" />
      </Router>
    </ServerLocation>
  );
  
  return `
    <!DOCTYPE html>
    <html>
      <head><title>My App</title></head>
      <body>
        <div id="root">${appHtml}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `;
};

// Express.js integration
app.get("*", (req, res) => {
  const html = renderApp(req.url);
  res.send(html);
});

// Next.js style server rendering
const getServerSideProps = async (context) => {
  const { req } = context;
  const url = req.url;
  
  return {
    props: {
      initialUrl: url
    }
  };
};

const Page = ({ initialUrl }) => (
  <ServerLocation url={initialUrl}>
    <App />
  </ServerLocation>
);

// URL with query parameters and hash
const renderWithQuery = () => {
  const url = "/search?q=react&category=library#results";
  
  return renderToString(
    <ServerLocation url={url}>
      <Router>
        <SearchResults path="/search" />
      </Router>
    </ServerLocation>
  );
};

// Server rendering with dynamic routes
const renderUserPage = (userId) => {
  const url = `/users/${userId}`;
  
  return renderToString(
    <ServerLocation url={url}>
      <Router>
        <UserProfile path="/users/:userId" />
      </Router>
    </ServerLocation>
  );
};

Server Location Context

Understanding the location context provided by ServerLocation for server-side rendering.

/**
 * ServerLocation provides a static location context
 */
interface ServerLocationContext {
  location: {
    pathname: string;
    search: string;
    hash: string;
  };
  navigate: () => never; // Throws error - navigation not allowed on server
}

Usage Examples:

// Components receive parsed location on server
const SearchResults = ({ location }) => {
  // On server: location = { pathname: "/search", search: "?q=react", hash: "" }
  const query = new URLSearchParams(location.search).get("q");
  
  return (
    <div>
      <h1>Search Results for: {query}</h1>
      <p>Server-rendered at {location.pathname}</p>
    </div>
  );
};

// Server rendering with hooks
const HookComponent = () => {
  const location = useLocation(); // Works on server with ServerLocation
  
  return (
    <div>
      <p>Current path: {location.pathname}</p>
      <p>Query string: {location.search}</p>
    </div>
  );
};

// Error handling for navigation attempts
const SafeNavigationComponent = () => {
  const navigate = useNavigate();
  
  const handleClick = () => {
    if (typeof window !== "undefined") {
      // Only navigate on client
      navigate("/new-page");
    } else {
      console.log("Navigation attempted on server - ignored");
    }
  };
  
  return <button onClick={handleClick}>Navigate</button>;
};

// Server-side route matching
const renderMatchedRoute = (url) => {
  return renderToString(
    <ServerLocation url={url}>
      <Router>
        <Home path="/" />
        <User path="/users/:userId" />
        <NotFound default />
      </Router>
    </ServerLocation>
  );
};

Server Rendering Patterns

Common patterns for implementing server-side rendering with Reach Router.

Usage Examples:

// Isomorphic application structure
const App = ({ serverUrl }) => {
  if (typeof window === "undefined") {
    // Server-side rendering
    return (
      <ServerLocation url={serverUrl}>
        <AppRoutes />
      </ServerLocation>
    );
  } else {
    // Client-side rendering
    return (
      <Router>
        <AppRoutes />
      </Router>
    );
  }
};

const AppRoutes = () => (
  <>
    <Home path="/" />
    <About path="/about" />
    <Contact path="/contact" />
  </>
);

// Server-side data fetching
const renderWithData = async (url) => {
  // Extract route parameters for data fetching
  const match = matchPath("/users/:userId", url);
  let userData = null;
  
  if (match) {
    userData = await fetchUser(match.params.userId);
  }
  
  return renderToString(
    <ServerLocation url={url}>
      <DataContext.Provider value={{ userData }}>
        <Router>
          <UserProfile path="/users/:userId" />
        </Router>
      </DataContext.Provider>
    </ServerLocation>
  );
};

// SEO optimization with server rendering
const renderSEOPage = (url, metadata) => {
  const appHtml = renderToString(
    <ServerLocation url={url}>
      <Router>
        <SEOPage path="/seo/*" metadata={metadata} />
      </Router>
    </ServerLocation>
  );
  
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <title>${metadata.title}</title>
        <meta name="description" content="${metadata.description}" />
        <meta property="og:url" content="${metadata.canonicalUrl}" />
      </head>
      <body>
        <div id="root">${appHtml}</div>
      </body>
    </html>
  `;
};

// Hydration-friendly server rendering
const createSSRApp = (url) => {
  const initialData = gatherInitialData(url);
  
  const html = renderToString(
    <ServerLocation url={url}>
      <DataProvider initialData={initialData}>
        <Router>
          <AppRoutes />
        </Router>
      </DataProvider>
    </ServerLocation>
  );
  
  return {
    html,
    initialData: JSON.stringify(initialData)
  };
};

// Server-side rendering with error boundaries
const SafeServerApp = ({ url }) => (
  <ServerLocation url={url}>
    <ErrorBoundary>
      <Router>
        <Home path="/" />
        <About path="/about" />
        <ErrorPage default />
      </Router>
    </ErrorBoundary>
  </ServerLocation>
);

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Something went wrong during server rendering.</div>;
    }
    
    return this.props.children;
  }
}

Client-Side Hydration

Handling the transition from server-rendered content to client-side routing.

Usage Examples:

// Client-side hydration setup
import { hydrate } from "react-dom";

const ClientApp = () => (
  <Router>
    <AppRoutes />
  </Router>
);

// Hydrate the server-rendered content
hydrate(<ClientApp />, document.getElementById("root"));

// Hydration with initial data
const hydrateWithData = (initialData) => {
  const App = () => (
    <DataProvider initialData={initialData}>
      <Router>
        <AppRoutes />
      </Router>
    </DataProvider>
  );
  
  hydrate(<App />, document.getElementById("root"));
};

// Read initial data from server
const initialDataScript = document.getElementById("initial-data");
const initialData = JSON.parse(initialDataScript.textContent);
hydrateWithData(initialData);

// Handling hydration mismatches
const HydrationSafeComponent = ({ children }) => {
  const [hasMounted, setHasMounted] = React.useState(false);
  
  React.useEffect(() => {
    setHasMounted(true);
  }, []);
  
  if (!hasMounted) {
    // Return server-safe content during hydration
    return <div>{children}</div>;
  }
  
  // Return client-specific content after hydration
  return <div className="client-hydrated">{children}</div>;
};

// Universal routing component
const UniversalApp = ({ initialUrl }) => {
  const [isServer] = React.useState(typeof window === "undefined");
  
  if (isServer) {
    return (
      <ServerLocation url={initialUrl}>
        <AppRoutes />
      </ServerLocation>
    );
  }
  
  return (
    <Router>
      <AppRoutes />
    </Router>
  );
};

// Progressive enhancement approach
const ProgressiveApp = () => {
  const [isHydrated, setIsHydrated] = React.useState(false);
  
  React.useEffect(() => {
    setIsHydrated(true);
  }, []);
  
  return (
    <div>
      {/* Always render core content */}
      <header>My App</header>
      
      {/* Conditionally render interactive features */}
      {isHydrated ? (
        <Router>
          <InteractiveRoutes />
        </Router>
      ) : (
        <StaticContent />
      )}
    </div>
  );
};

Install with Tessl CLI

npx tessl i tessl/npm-reach--router

docs

advanced-routing.md

history.md

hooks.md

index.md

location-context.md

navigation.md

routing.md

server-rendering.md

utilities.md

tile.json