Next generation React routing library with accessible navigation and focus management.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Components and utilities for rendering routes on the server with static location context.
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 locationchildren (React.ReactNode) - Child components that will receive location contextUsage 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>
);
};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>
);
};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;
}
}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