@webbyx/next-js
v0.1.0
Published
Blazing fast development with NextJs helpers
Readme
@webbyx/next-js
Blazing-fast helpers for Next.js apps — opinionated wrappers around Apollo Client, authentication flows, and route progress UI. Works with both the Pages Router (HOC pattern) and the App Router (provider + server-helper pattern).
Table of contents
- Installation
- Quick start
- Pages Router usage
- App Router usage
- Shared APIs
- TypeScript types
- Compatibility matrix
- FAQ
- Changelog
Installation
npm install @webbyx/next-js
# or
yarn add @webbyx/next-jsThe package ships with @apollo/client, graphql, graphql-ws, @apollo/client-integration-nextjs, rxjs, next, react, react-dom, nookies, and styled-components as direct dependencies — no extra installs are required to use any of the features.
Subpath exports. The library exposes three entrypoints:
| Import path | Use for | | ---------------------------- | ------------------------------------------------------------------------------------------------- | |
@webbyx/next-js| Pages Router (HOCs, factories, helpers) and shared types/hooks | |@webbyx/next-js/app| App Router client-safe APIs (providers, hooks, components) | |@webbyx/next-js/app/server| App Router server-only APIs (getAuthSession,createServerApolloClient, server cookie helpers) |
Quick start
Pages Router
// pages/_app.tsx
import type { AppProps } from "next/app";
import {
withApolloClient,
withAuthIdentity,
withRouteIndicator
} from "@webbyx/next-js";
import { apolloOptions } from "../config/apollo";
import { authOptions } from "../config/auth";
const MyApp = ({ Component, pageProps }: AppProps) => (
<Component {...pageProps} />
);
export default withRouteIndicator()(
withApolloClient({ ssr: true, options: apolloOptions })(
withAuthIdentity({ ssr: true, apollo: true, options: authOptions })(MyApp)
)
);App Router
// app/providers.tsx
"use client";
import {
ApolloAppProvider,
AuthIdentityProvider,
RouteIndicator
} from "@webbyx/next-js/app";
import { apolloOptions } from "../config/apollo";
import { authConfig } from "../config/auth";
export const Providers = ({
children,
initialAuthUser,
initialConfig
}: {
children: React.ReactNode;
initialAuthUser: User | null;
initialConfig: Record<string, unknown>;
}) => (
<ApolloAppProvider options={apolloOptions}>
<AuthIdentityProvider
config={authConfig}
initialAuthUser={initialAuthUser}
initialConfig={initialConfig}>
<RouteIndicator />
{children}
</AuthIdentityProvider>
</ApolloAppProvider>
);// app/layout.tsx
import { getAuthSession } from "@webbyx/next-js/app/server";
import { authConfig } from "../config/auth";
import { Providers } from "./providers";
import { headers } from "next/headers";
export default async function RootLayout({
children
}: {
children: React.ReactNode;
}) {
const pathname = (await headers()).get("x-pathname") ?? "/";
const { authUser, config } = await getAuthSession(
{ pathname },
authConfig.options,
/* redirect */ false
);
return (
<html lang="en">
<body>
<Providers initialAuthUser={authUser} initialConfig={config}>
{children}
</Providers>
</body>
</html>
);
}Pages Router usage
All Pages-Router APIs are imported from the package root:
import {
withApolloClient,
withAuthIdentity,
withAuthServerSideProps,
withRouteIndicator,
useAuthIdentity,
AuthIdentityContext,
createApolloClient,
createApolloContext,
createAuthConfigProps,
getCookie,
setCookie,
destroyCookie,
setAuthToken,
clearAuthToken,
retrieveAuthTokenFromContext,
redirect
} from "@webbyx/next-js";withApolloClient
HOC that mounts an Apollo Client on the page tree and (optionally) hydrates the cache at SSR time.
// pages/_app.tsx
import { withApolloClient } from "@webbyx/next-js";
const MyApp = (props) => <props.Component {...props.pageProps} />;
export default withApolloClient({
ssr: true,
options: {
graphqlRequest: { uri: "https://api.example.com/graphql" },
websocketRequest: { url: "wss://api.example.com/graphql" },
getAuthToken: async (ctx) => retrieveAuthTokenFromContext(ctx)
}
})(MyApp);| Prop | Type | Description |
| --------- | --------------------------------------------- | ---------------------------------------------------------- |
| ssr | boolean | Run getInitialProps and extract Apollo cache during SSR. |
| options | ApolloClientOptions | Apollo configuration (links, cache, websocket, auth). |
withAuthIdentity
HOC that attaches an authenticated session, manages cross-tab signout, and computes redirection rules from AuthOptions.paths.
// pages/_app.tsx
import { withApolloClient, withAuthIdentity } from "@webbyx/next-js";
const MyApp = (props) => <props.Component {...props.pageProps} />;
export default withApolloClient({ ssr: true, options: apolloOptions })(
withAuthIdentity({
ssr: true,
apollo: true,
options: {
checkAuthProfile: async () => fetchMe(),
refreshAccessToken: async () => refreshToken(),
paths: {
signInPath: "/signin",
afterAuthPath: "/app",
beforeAuthPath: "/",
allowedBeforeAuthPaths: ["/", "/signin", "/signup"],
restrictAfterAuthPaths: ["/signin", "/signup"]
}
}
})(MyApp)
);| Prop | Type | Description |
| --------- | ----------------------------- | -------------------------------------------------------------------------------- |
| ssr | boolean | Resolve the auth user during getInitialProps. |
| apollo | boolean | When true, requires withApolloClient and resets the Apollo cache on signout. |
| options | AuthOptions | Auth resolver, paths, and redirection rules. |
withAuthServerSideProps
A getServerSideProps factory you can drop into individual pages when you want per-page SSR auth resolution (instead of app-wide withAuthIdentity({ ssr: true })).
// pages/dashboard.tsx
import { withAuthServerSideProps } from "@webbyx/next-js";
export const getServerSideProps = withAuthServerSideProps(
{ ...authOptions, redirection: true },
({ authUser }) =>
async () => ({
props: { dashboardData: await fetchDashboard(authUser) }
})
);withRouteIndicator
A top-of-page progress bar driven by Next.js Router.events.
// pages/_app.tsx
import { withRouteIndicator } from "@webbyx/next-js";
const MyApp = (props) => <props.Component {...props.pageProps} />;
export default withRouteIndicator({ color: "#f85656", height: 4 })(MyApp);| Option | Type | Default | Description |
| -------- | -------- | --------- | --------------------- |
| color | string | #f85656 | Bar color. |
| height | number | 4 | Bar height in pixels. |
App Router usage
App-Router APIs are split between two entrypoints. Client-safe APIs come from @webbyx/next-js/app; server-only APIs come from @webbyx/next-js/app/server. Importing a server-only export into a client component will fail at runtime — keep the boundary clean.
// Client-safe (use in client components, layouts, etc.)
import {
ApolloAppProvider,
AuthIdentityProvider,
RouteIndicator,
buildApolloLink
} from "@webbyx/next-js/app";
// Server-only (use in server components / route handlers / server actions)
import {
createServerApolloClient,
getAuthSession,
getServerCookie,
setServerCookie,
deleteServerCookie,
getServerAuthToken
} from "@webbyx/next-js/app/server";<ApolloAppProvider>
Streaming-aware Apollo provider built on @apollo/client-integration-nextjs. Wrap your app's client tree once.
// app/providers.tsx
"use client";
import { ApolloAppProvider } from "@webbyx/next-js/app";
export const Providers = ({ children }: { children: React.ReactNode }) => (
<ApolloAppProvider
options={{
graphqlRequest: { uri: "https://api.example.com/graphql" },
websocketRequest: { url: "wss://api.example.com/graphql" }
}}>
{children}
</ApolloAppProvider>
);| Prop | Type | Description |
| --------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| options | ApolloClientOptions | Same shape as the Pages Router. The provider uses the streaming-aware ApolloClient from @apollo/client-integration-nextjs so server-rendered queries hydrate correctly. |
createServerApolloClient
Per-request Apollo Client for use inside React Server Components, route handlers, and server actions.
// lib/apollo-server.ts
import {
createServerApolloClient,
getServerAuthToken
} from "@webbyx/next-js/app/server";
export const { getClient, query, PreloadQuery } = createServerApolloClient({
graphqlRequest: { uri: "https://api.example.com/graphql" },
getAuthToken: () => getServerAuthToken()
});// app/dashboard/page.tsx
import { query } from "@/lib/apollo-server";
import { GET_DASHBOARD } from "@/queries";
export default async function DashboardPage() {
const { data } = await query({ query: GET_DASHBOARD });
return <Dashboard data={data} />;
}Returned API: { getClient, query, PreloadQuery } from @apollo/client-integration-nextjs.
<AuthIdentityProvider>
Drop-in replacement for withAuthIdentity that subscribes to usePathname / useSearchParams from next/navigation. Hydrate it from getAuthSession.
"use client";
import { AuthIdentityProvider } from "@webbyx/next-js/app";
export const AuthBoundary = ({
children,
initialAuthUser,
initialConfig
}: Props) => (
<AuthIdentityProvider
config={{
apollo: true,
options: {
checkAuthProfile: async () => fetchMe(),
refreshAccessToken: async () => refreshToken(),
paths: {
/* same shape as Pages Router */
}
}
}}
initialAuthUser={initialAuthUser}
initialConfig={initialConfig}>
{children}
</AuthIdentityProvider>
);| Prop | Type | Description |
| ----------------- | --------------------------- | --------------------------------------------------------- |
| config | AuthConfig | { apollo, options } — same as Pages Router minus ssr. |
| initialAuthUser | T \| null | Hydrated from getAuthSession on the server. |
| initialConfig | Record<string, unknown> | Hydrated derived config (e.g. shouldHideLayout). |
When config.apollo is true, the provider must be a child of <ApolloAppProvider> so it can clear the cache on sign-out.
getAuthSession
Server-side auth resolver. Reads the access cookie, calls checkAuthProfile, computes createAuthConfigProps, and (optionally) issues a next/navigation redirect for protected routes.
// app/(authenticated)/layout.tsx
import { getAuthSession } from "@webbyx/next-js/app/server";
import { headers } from "next/headers";
import { authConfig } from "@/config/auth";
export default async function ProtectedLayout({
children
}: {
children: React.ReactNode;
}) {
const pathname = (await headers()).get("x-pathname") ?? "/";
const { authUser, config } = await getAuthSession(
{ pathname },
authConfig.options,
/* redirect */ true
);
return (
<Authed user={authUser} config={config}>
{children}
</Authed>
);
}| Argument | Type | Description |
| ---------- | --------------------------------------- | -------------------------------------------------------------------------------------------- |
| context | { pathname: string; asPath?: string } | Current request path. Pull from middleware-set headers or params. |
| options | AuthOptions | Same as Pages Router. |
| redirect | boolean (default true) | When true, the helper calls next/navigation's redirect() for any computed redirection. |
Returns: { authUser, config, redirection }.
<RouteIndicator> (App Router)
Top-loading-bar that pulses on every navigation by watching usePathname + useSearchParams. Mount once near the root.
// app/layout.tsx
import { RouteIndicator } from "@webbyx/next-js/app";
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<RouteIndicator color="#f85656" height={4} />
{children}
</body>
</html>
);
}Same color / height options as the Pages Router HOC.
Shared APIs
useAuthIdentity
Consume the auth context from anywhere inside <AuthIdentityProvider> or withAuthIdentity.
import { useAuthIdentity } from "@webbyx/next-js"; // also re-exported from /app via the auth context
const Header = () => {
const { authUser, signOut } = useAuthIdentity<User>();
return authUser ? (
<button onClick={signOut}>Sign out</button>
) : (
<SignInLink />
);
};Returned shape (AuthIdentityProps<T>):
| Field | Type | Description |
| --------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| authUser | T \| null | Current user. |
| config | Record<string, any> | Computed redirection / layout config. |
| signIn(token) | (token: string) => void | Persist token and navigate to paths.afterAuthPath. |
| signOut() | () => Promise<void> | Reset Apollo cache (if enabled), clear token, broadcast sign-out across tabs, navigate to paths.beforeAuthPath. |
| refetch() | () => Promise<T \| null> | Re-run checkAuthProfile. |
| setAuthUser(user?) | (user?: T) => void | Optimistically set the user. |
| setAuthToken(token) | (token: string) => void | Persist a token without navigation. |
| refreshToken() | () => Promise<void> | Calls options.refreshAccessToken. |
| clearAuthToken() | () => void | Drop the cookie locally. |
Factories
Useful when you need raw access (e.g. building a custom test harness or wiring Apollo into a custom server framework).
| Export | Where it lives | Purpose |
| ---------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| createApolloClient(initialState, options, context) | @webbyx/next-js | Pages-Router Apollo factory used internally by withApolloClient. |
| initApolloClient(initialState, options, context) | @webbyx/next-js | Singleton-on-client / fresh-on-server wrapper. |
| createApolloContext(options, ctx) | @webbyx/next-js | Installs an Apollo client onto a NextPageContext for use inside getStaticProps / getServerSideProps. |
| createAuthConfigProps(ctx, authUser, options, redirect?) | @webbyx/next-js | Pure path-rules engine — returns { redirection, config }. Reused by both routers. |
| buildApolloLink(options, context) | @webbyx/next-js/app | Shared link/cache builder — mirrors the Pages-Router link chain (auth, error, http, optional GraphQL-WS). Useful if you want to assemble a custom Apollo client on top. |
| createServerApolloClient(options) | @webbyx/next-js/app | RSC-scoped Apollo client (see above). |
Helpers
import {
// cookie helpers (Pages Router context-aware)
getCookie,
setCookie,
destroyCookie,
eraseCookieFromAllPaths,
// token helpers
setAuthToken,
clearAuthToken,
setAuthTokenToContext,
retrieveAuthTokenFromContext,
getTokenFromCookies,
// navigation (Pages Router only)
redirect
} from "@webbyx/next-js";| Helper | Signature | Notes |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| getCookie(key, ctx?) | (string, NextContextPayload?) => string \| null | Reads from nookies (server: from ctx.req; client: from document.cookie). |
| setCookie(key, value, ctx?, days?, path?) | | days defaults to 30, path to /. |
| destroyCookie(key, ctx) | | Also calls eraseCookieFromAllPaths to scrub legacy entries. |
| setAuthToken(token, key?, days?) | | Wrapper around setCookie keyed by accessTokenKey (default "token"). |
| clearAuthToken(key?) | | Removes the auth cookie everywhere. |
| retrieveAuthTokenFromContext(ctx?, key?) | | Auto-detects AppContext vs NextPageContext. |
| redirect(ctx, target, method?) | method ∈ "replace" (default) | "push" | "reload" | "force-reload" | "external" | On the server it writes a 303; on the client it goes through next/router. |
Server cookies (App Router only)
Not part of the Pages-Router helper set — these use next/headers and are async.
import {
getServerCookie,
setServerCookie,
deleteServerCookie,
getServerAuthToken
} from "@webbyx/next-js/app/server";
const token = await getServerAuthToken(); // defaults to "token"
await setServerCookie("locale", "en", /* days */ 365);
await deleteServerCookie("legacy_session");TypeScript types
All public types are exported from the package root and re-exported by /app where relevant.
ApolloClientOptions
type ApolloClientOptions = Omit<
ApolloClient.Options,
"uri" | "credentials" | "headers" | "cache" | "ssrMode"
> & {
graphqlRequest: { uri: string; credentials?: RequestCredentials };
cache?: ApolloCache;
cacheConfig?: InMemoryCacheConfig;
websocketRequest?: WsClientOptions | ((ctx?: any) => WsClientOptions);
websocketEvents?: WsEventListener;
getAuthToken?: (ctx?: any) => Promise<string | null>;
constructApolloLinks?: (
links: {
errorLink: ApolloLink;
authLink: ApolloLink;
httpLink: ApolloLink;
},
ctx: any
) => ApolloLink[];
connectToDevTools?: boolean;
};WsEventListener
onPing, onPong, onError, onOpened, onClosed, onMessage, onConnected, onConnecting — all optional, all from graphql-ws.
AuthOptions
type AuthOptions<T = any> = {
paths: AuthPaths;
logging?: boolean;
enableAdmin?: boolean;
enableOnboarding?: boolean;
enableNotFoundRedirection?: boolean;
valueKeyAdmin?: string; // default: "isAdmin"
valueKeyOnboard?: string; // default: "setupIsRequired"
accessTokenKey?: string; // default: "token"
accessTokenValidityDays?: number; // default: 30
syncAuthEventKeyName?: string; // localStorage key for cross-tab signout
checkAuthProfile: (ctx?: any) => Promise<T | null>;
refreshAccessToken?: (ctx?: any) => Promise<string>;
checkRequiredOnboard?: (user: T) => Promise<boolean>;
onChangePath?: (paths: string[], user?: T) => Promise<string | false>;
onConstructConfig?: (
paths: string[],
user?: T
) => Promise<Record<string, any> | false>;
};AuthPaths
type AuthPaths = {
notFoundPath?: string;
signInPath: string;
afterAuthPath: string;
beforeAuthPath: string;
onboardingPath?: string;
allowedBeforeAuthPaths: string[];
restrictAfterAuthPaths: string[];
allowedAdminPaths?: string[];
noLayoutPaths?: string[];
};AuthConfig
type AuthConfig = {
ssr?: boolean; // Pages Router only
apollo?: boolean;
options: AuthOptions;
};RouteIndicatorOptions
type RouteIndicatorOptions = { color?: string; height?: number };Compatibility matrix
| Peer | Tested with |
| ----------------------------------- | ----------- |
| next | ^16.0.0 |
| react / react-dom | ^19.0.0 |
| @apollo/client | ^4.0.0 |
| @apollo/client-integration-nextjs | ^0.14.0 |
| graphql | ^16.0.0 |
| graphql-ws | ^6.0.0 |
| styled-components | ^6.0.0 |
FAQ
Can I mix the two routers in one project?
Yes. Pages Router code stays under pages/, App Router code under app/. Import HOCs from @webbyx/next-js for the former and providers from @webbyx/next-js/app for the latter — they don't share runtime state, so make sure each tree gets its own provider/HOC.
Why does <AuthIdentityProvider> need an initial user?
The hydration is done on the server through getAuthSession. Passing initialAuthUser and initialConfig avoids a client-side flicker while the user resolves.
Why is lib/dom pinned to ES2020 in tsconfig.json?
The current ESLint stack (@typescript-eslint/parser 4.x, eslint 7.x) can't parse newer lib values. Modernizing the lint stack is a separate task; the runtime itself supports modern targets.
Subscriptions + App Router?
Set options.websocketRequest on <ApolloAppProvider> exactly like the Pages Router. The link is only attached on the browser, so RSC queries are unaffected.
Changelog
v0.0.7 (unreleased)
- Bumped Next.js to 16.2.4, React to 19.2.5, Apollo Client to 4.1.9.
- Added App Router support under the
./appsubpath:<ApolloAppProvider>,createServerApolloClient,<AuthIdentityProvider>,getAuthSession,<RouteIndicator>, server cookie helpers. - Modernized
tsconfig.json; cleaned up redundant default exports.
v0.0.6
- See
git logfor previous releases.
