goji-next
v1.2.10
Published
Full-stack framework utilities for Next.js applications
Maintainers
Readme
Goji Next
Full-stack framework utilities for Next.js applications with type-safe navigation, server utilities, client-side tools, and development helpers.
Installation
npm install goji-nextQuick Start
1. Create Configuration File
Create a goji.config.ts file in your project root:
import type { IGojiConfig } from "goji-next/types";
export const gojiConfig = {
api: {
urls: [
{
name: "my-api",
url: "https://api.example.com",
openapi: "https://api.example.com/openapi.json",
},
],
},
navigation: {
ignorePrefix: undefined,
},
paths: {
serverSidePath: "./server",
clientSidePath: "./view",
appPath: "./app",
testsPath: "./cypress",
testMocksPath: "@cypress/support/mocks",
pageWrapperPath: "view/components/Page",
pagesPath: "view/pages",
},
} as const satisfies IGojiConfig;The configuration file is optional. If not provided, Goji will use default values with an empty API urls array.
Modules
goji-next/navigation
Type-safe navigation system for Next.js with middleware support.
Setup
1. Create Navigation Config
Create navigation/index.ts in your project:
import { createGojiNavigation } from "goji-next/navigation";
import type { INavigationConfig } from "goji-next/navigation";
import { gojiConfig } from "../goji.config";
// Define your page names
export type PageName = "home" | "products" | "product-detail";
// Define page params for each route
export interface PageParams {
home: {};
products: {};
"product-detail": { id: string };
}
// Define search params for each route
export interface SearchParams {
home: {};
products: { category?: string; page?: number };
"product-detail": {};
}
export const navigationConfig: INavigationConfig<PageName> = {
routes: {
home: { route: "/" },
products: { route: "/products" },
"product-detail": { route: "/products/[id]" },
},
redirects: {},
masks: {},
searchParamsSchemas: {},
ignorePrefix: gojiConfig.navigation.ignorePrefix,
};
const navigation = createGojiNavigation<PageName, PageParams, SearchParams>(
navigationConfig
);
export const {
link,
redirect,
useRouter,
usePage,
usePathname,
notFound,
useQueryState,
getSearchParamsFromUrl,
} = navigation;2. Add Middleware
Create or update middleware.ts:
import { navigationMiddleware } from "goji-next/navigation";
import { NextRequest, NextResponse } from "next/server";
import { navigationConfig } from "./navigation";
export default function middleware(request: NextRequest) {
const navigationResponse = navigationMiddleware({
redirects: navigationConfig.redirects,
masks: navigationConfig.masks,
ignorePrefix: navigationConfig.ignorePrefix,
request,
});
if (navigationResponse) {
navigationResponse.headers.set("x-pathname", request.nextUrl.pathname);
return navigationResponse;
}
const response = NextResponse.next();
response.headers.set("x-pathname", request.nextUrl.pathname);
return response;
}
export const config = {
matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
};3. Update Next Config
Update next.config.ts:
import { withNavigationConfig } from "goji-next/navigation";
import type { NextConfig } from "next";
import { navigationConfig } from "./navigation";
const nextConfig: NextConfig = {
// ... your config
};
export default withNavigationConfig({
redirects: navigationConfig.redirects,
ignorePrefix: navigationConfig.ignorePrefix,
nextConfig,
});Usage
Type-safe Links:
import { link } from "./navigation";
// Simple route
const homeUrl = link("home");
// Route with params
const productUrl = link("product-detail", { id: "123" });
// Route with search params
const productsUrl = link("products", {}, { category: "electronics", page: 1 });Server-side Redirects:
import { redirect } from "./navigation";
export default async function ServerComponent() {
const isAuthenticated = await checkAuth();
if (!isAuthenticated) {
redirect("home"); // Type-safe redirect
}
// ... component logic
}Client-side Navigation:
'use client';
import { useRouter } from './navigation';
export function MyComponent() {
const router = useRouter();
const handleClick = () => {
router.push('product-detail', { params: { id: '123' } });
};
return <button onClick={handleClick}>View Product</button>;
}Access Page Info:
"use client";
import { usePage } from "./navigation";
export function MyComponent() {
const { name, params, searchParams } = usePage();
console.log(name); // Current page name
console.log(params); // URL params (typed)
console.log(searchParams); // Search params (typed)
}Parse URL Parameters:
import { getSearchParamsFromUrl } from "./navigation";
// Option 1: With explicit page type (recommended for type safety)
const urlData = getSearchParamsFromUrl<"products">(
"/products?category=electronics&page=2"
);
console.log(urlData.basePath); // '/products'
console.log(urlData.params); // { category: 'electronics', page: 2 } (typed as SearchParams['products'])
// Option 2: Without type parameter (params will be union of all SearchParams)
const { basePath } = getSearchParamsFromUrl("/products");
console.log(basePath); // '/products'
// Useful for server-side URL parsing
export async function ServerComponent() {
const url = "/products?category=books";
const { params } = getSearchParamsFromUrl<"products">(url);
// params is fully typed as SearchParams['products']
const products = await fetchProducts(params.category);
// ...
}goji-next/server
Server-side utilities for Next.js server components and API routes.
PageProvider
Wrapper for server components with error boundary:
import { PageProvider } from 'goji-next/server';
export default async function Page() {
return (
<PageProvider>
<YourContent />
</PageProvider>
);
}Resource
Type-safe API resource builder:
import { Resource } from 'goji-next/server';
const userResource = new Resource({
name: 'users',
baseUrl: 'https://api.example.com',
});
// In server component
export default async function UsersPage() {
const users = await userResource.list();
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}HttpError
HTTP error handling:
import { HttpError } from "goji-next/server";
export async function GET() {
const user = await getUser();
if (!user) {
throw new HttpError(404, "User not found");
}
return Response.json(user);
}Cookies
Server and client-side cookie management:
import {
getCookie,
setCookie,
deleteCookie,
CookieManager,
} from "goji-next/server";
// Server component
export default async function ServerComponent() {
const token = await getCookie("auth_token");
if (!token) {
// Handle unauthenticated state
}
}
// Client component (works in browser)
("use client");
import { CookieManager } from "goji-next/server";
export function ClientComponent() {
const handleLogin = async (token: string) => {
await CookieManager.set("auth_token", token, {
expires: 7, // 7 days
secure: true,
sameSite: "strict",
});
};
// Falls back to localStorage if cookies are rejected
const handleLogout = () => {
CookieManager.delete("auth_token");
};
}goji-next/client
Client-side utilities for React components.
Local Storage
Type-safe localStorage wrapper:
import { localStorageManager } from "goji-next/client";
// In client component
export function MyComponent() {
const handleSave = () => {
localStorageManager.set("user_preference", "dark-mode");
};
const handleLoad = () => {
const preference = localStorageManager.get("user_preference");
console.log(preference); // 'dark-mode' or null
};
const handleClear = () => {
localStorageManager.delete("user_preference");
};
}Modals System
Global modal management:
1. Setup Provider:
// app/layout.tsx
import { ModalsProvider } from 'goji-next/client';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ModalsProvider>
{children}
</ModalsProvider>
</body>
</html>
);
}2. Create Modal Component:
// components/MyModal.tsx
'use client';
import type { IBaseModalProps } from 'goji-next/client';
interface MyModalProps extends IBaseModalProps {
title: string;
onConfirm: () => void;
}
export function MyModal({ title, onConfirm, onClose }: MyModalProps) {
return (
<div>
<h2>{title}</h2>
<button onClick={onConfirm}>Confirm</button>
<button onClick={onClose}>Cancel</button>
</div>
);
}3. Use Modals:
'use client';
import { openModal, closeModal, useModal } from 'goji-next/client';
import { MyModal } from './components/MyModal';
export function MyComponent() {
const handleOpenModal = () => {
const modalId = openModal(MyModal, {
title: 'Confirm Action',
onConfirm: () => {
console.log('Confirmed!');
closeModal(modalId);
},
});
};
return <button onClick={handleOpenModal}>Open Modal</button>;
}
// Or use the hook
export function AnotherComponent() {
const { open, close } = useModal(MyModal);
const handleOpen = () => {
open({
title: 'Hello',
onConfirm: () => {
console.log('Done');
close();
},
});
};
return <button onClick={handleOpen}>Open</button>;
}goji-next/dev
Development tools and utilities.
ESLint Config
// eslint.config.js
import { eslintConfig } from "goji-next/dev";
export default eslintConfig;Plop Generators
// plopfile.js
import { plopConfig } from "goji-next/dev";
export default plopConfig;Dev Server
The package includes a custom dev server with a beautiful startup banner:
{
"scripts": {
"dev": "node node_modules/goji-next/dist/dev/start/index.js",
"dev:turbo": "node node_modules/goji-next/dist/dev/start/index.js --turbopack",
"dev:mocked": "cross-env NEXT_PUBLIC_MOCKS_ENABLED=1 node node_modules/goji-next/dist/dev/start/index.js --turbopack"
}
}Type Generation
Generate TypeScript types from OpenAPI schemas:
{
"scripts": {
"generate-api-types": "tsx node_modules/goji-next/dist/server/generate-types/index.ts"
}
}Code Generation (Plop)
Use the built-in Plop generators for scaffolding:
{
"scripts": {
"cursorich": "plop"
}
}You'll need a plopfile.js:
import { plopConfig } from "goji-next/dev";
export default plopConfig;Cypress Testing
Cypress testing utilities for E2E and screenshot tests:
{
"scripts": {
"test:e2e": "start-server-and-test dev:mocked http://localhost:3000 \"cypress run --config specPattern=cypress/e2e/**/*.cy.{js,jsx,ts,tsx}\"",
"test:e2e-debug": "concurrently -n Dev,Cypress -c blue,yellow \"npm run dev:mocked\" \"cypress open --config specPattern=cypress/e2e/**/*.cy.{js,jsx,ts,tsx}\"",
"test:screenshots": "node node_modules/goji-next/dist/dev/cypress/scripts/run-test-wrapper.cjs node_modules/goji-next/dist/dev/cypress/scripts/docker-test-component.sh",
"test:screenshots-update": "node node_modules/goji-next/dist/dev/cypress/scripts/run-test-wrapper.cjs node_modules/goji-next/dist/dev/cypress/scripts/docker-update-snapshots.sh"
}
}Linting
Use with the built-in ESLint config:
{
"scripts": {
"lint": "eslint app view types server router --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint app view types server router --ext .ts,.tsx,.js,.jsx --fix && prettier --write \"app/**/*.{ts,tsx,js,jsx}\" \"view/**/*.{ts,tsx,js,jsx}\" \"types/**/*.{ts,tsx,js,jsx}\" \"server/**/*.{ts,tsx,js,jsx}\""
}
}Complete Example
Here's a full package.json scripts section using Goji utilities:
{
"scripts": {
"dev": "node node_modules/goji-next/dist/dev/start/index.js --turbopack",
"dev:mocked": "cross-env NEXT_PUBLIC_MOCKS_ENABLED=1 node node_modules/goji-next/dist/dev/start/index.js --turbopack",
"build": "next build",
"start": "next start",
"lint": "eslint app view types server router --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint app view types server router --ext .ts,.tsx,.js,.jsx --fix && prettier --write \"**/*.{ts,tsx,js,jsx}\"",
"test:e2e": "start-server-and-test dev:mocked http://localhost:3000 \"cypress run\"",
"test:e2e-debug": "concurrently -n Dev,Cypress \"npm run dev:mocked\" \"cypress open\"",
"test:screenshots": "node node_modules/goji-next/dist/dev/cypress/scripts/run-test-wrapper.cjs node_modules/goji-next/dist/dev/cypress/scripts/docker-test-component.sh",
"test:screenshots-update": "node node_modules/goji-next/dist/dev/cypress/scripts/run-test-wrapper.cjs node_modules/goji-next/dist/dev/cypress/scripts/docker-update-snapshots.sh",
"generate-api-types": "tsx node_modules/goji-next/dist/server/generate-types/index.ts",
"cursorich": "plop"
}
}TypeScript Support
Goji is written in TypeScript and provides full type safety out of the box. All modules export their types:
import type { IGojiConfig } from "goji-next/types";
import type { INavigationConfig } from "goji-next/navigation";
import type { IBaseModalProps } from "goji-next/client";
import type { HttpErrorStatusCode } from "goji-next/server";License
MIT
