next-era
Welcome to **Next Era**! A comprehensive library designed to supercharge your **Next.js** applications with powerful utilities and significant performance optimizations. Build faster, more efficient, and feature-rich Next.js projects with ease.
Welcome to Next Era! A comprehensive library designed to supercharge your Next.js applications with powerful utilities and significant performance optimizations. Build faster, more efficient, and feature-rich Next.js projects with ease.
Next Era is a powerful and flexible utility package designed to supercharge your Next.js applications. Built with a deep understanding of Next.js principles and leveraging the power of Vercel and Lodash, Next Era enhances performance, developer experience, and adds advanced features to your SSR, SSG, and CSR Next.js projects.
Tired of boilerplate or looking for optimized solutions for common Next.js tasks? Next Era provides a suite of fast, minimalist tools to boost your productivity and streamline your workflows, helping you build faster and more efficient applications.
- Intuitive and Developer-Friendly Utilities: Designed for ease of use and seamless integration into your Next.js workflow.
- Leverages Next.js, Vercel, and Lodash: Built on a solid foundation of industry-leading technologies for reliability and performance.
- Optimized Data Fetching (Powered by SWC): Experience improved efficiency and speed in your data fetching operations.
- Effortless Progressive Web App (PWA) Integration: Transform your Next.js app into a PWA with streamlined offline support and enhanced user engagement.
Install via npm, yarn, or pnpm:
npm install next-era
# or
yarn add next-era
# or
pnpm add next-era
Next Era helps turn your Next.js application into a Progressive Web App (PWA) with efficient caching strategies to optimize performance and offline access.
Comparison of using Next.js
+ next-era
This plugin generates a sw.js
file with predefined caching strategies to optimize loading times while maintaining continuous development and deployment workflows.
By default, we have a static resource strategy and 3 fetching strategies [Reference] includes:
During the Service Worker installation phase, configured static resources are preloaded and cached for quick access.
By default, all of assets in public
folder will be considered as is the static resources and will be prefetched automatically.
- Example: in next.config.mjs
import { NextEraPlugin } from "next-era/sw";
const nextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.plugins.push(
new NextEraPlugin({
sw: {
resources: [
"/manifest.webmanifest",
"/opengraph-image.png",
"/favicon.ico",
], // all of assets in `public` folder and 3 assets as configuration will be prefetched
},
}),
);
}
return config;
},
};
Retrieves resources from the cache first, falling back to network fetching if needed. Best for static assets.
- Example: in next.config.mjs
import { NextEraPlugin } from "next-era/sw";
const nextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.plugins.push(
new NextEraPlugin({
sw: {
resources: [
"/manifest.webmanifest",
"/opengraph-image.png",
"/favicon.ico",
],
strategy: {
cf: ["/not-found.png"], // Apply for static resources like not-found image
},
},
}),
);
}
return config;
},
};
Fetches from the network first, caching responses for future use. Ideal for dynamic data like API requests.
- Example: in next.config.mjs
import { NextEraPlugin } from "next-era/sw";
const nextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.plugins.push(
new NextEraPlugin({
sw: {
resources: [
"/manifest.webmanifest",
"/opengraph-image.png",
"/favicon.ico",
],
strategy: {
cf: ["/not-found.png"],
nf: ["/api/**"], // Apply for API fetching
},
},
}),
);
}
return config;
},
};
Serves cached data while simultaneously fetching and updating it from the network. Suitable for page/layout data.
- Example: in next.config.mjs
import { NextEraPlugin } from "next-era/sw";
const nextConfig = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.plugins.push(
new NextEraPlugin({
sw: {
resources: [
"/manifest.webmanifest",
"/opengraph-image.png",
"/favicon.ico",
],
strategy: {
cf: ["/not-found.png"],
nf: ["/api/**"],
swr: ["/**"], // others thing such as page/layout
},
},
}),
);
}
return config;
},
};
[!CAUTION] The order of strategies should be
CF → NF → SWR
to ensure optimal request handling.
[!IMPORTANT] In Development mode, the Service Worker strategy will always be set to
/**
forNF
to ensure the freshest data is fetched during coding.
This component registers events for sw.js
into the Service Worker. Place it inside layout.tsx
within the app
folder.
Looking into the hook component, you can see sw.js?v=
a Service Worker script URI that be attached versioning to trigger updating after code changed.
- Example: in next.config.mjs
import { NextEraWorker } from "next-era/sw";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<NextEraWorker />
{children}
</body>
</html>
);
}
This is the script file that will be registered into the Service Worker. sw.js
is dynamically generated and updated during build time, then placed into the public
folder of the project.
Enhanced version of React's useActionState
, allowing manual state updates via setState
.
- Original:
import { useActionState } from "react";
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined,
);
- Enhanced:
import { useActionState } from "next-era/hook";
const [errorMessage, formAction, isPending, setErrorMessage] = useActionState(
authenticate,
undefined,
);
// `setErrorMessage` allows manual updates to `errorMessage`
A hook for managing boolean state.
- Example:
import { useBool } from "next-era/hook";
const [isEditing, enableEditing, disableEditing] = useBool();
A flexible hook for fetching API data.
-
Requires: A base API URL configured in
.env
or passed as an option.NEXT_ERA_API_URL
orNEXT_PUBLIC_NEXT_ERA_API_URL
(if you're working on NextJS) -
Uses:
useFetch
(inspired by SWR) for optimized data fetching.useFetch
for enhanced pass params/queries. -
Example:
import { useFetch } from "next-era/hook";
const [values, fetchValues] = useFetch<ExampleType[]>(
UseFetchMethodEnum.GET,
"/api/example/route",
{
formatter: async ({ data }) =>
data.map(({ name, id }) => ({ label: name, value: id })),
},
);
Enhanced version of Next.js' useRouter
with improved path handling.
- Example:
import { useRouter } from "next-era/hook";
const { push } = useRouter();
return (
<div
onClick={() =>
push({
path: "/example/route/id/:id/detail",
options: {
params: { id },
searchParams: { page: 1, limit: 10 },
},
})
}
/>
);
- Example #2:
import { useRouter } from "next-era/hook";
const { toHref } = useRouter();
return (
<Link
href={toHref({
path: "/example/route/id/:id/detail",
options: {
params: { id },
searchParams: { page: 1, limit: 10 },
},
})}
/>
);
Factory function for creating DTO instances.
- Example:
import { Factory } from "next-era/db";
await Factory<ToType>(fromObject).to(toDTO);
- Example #2:
import { Factory } from "next-era/db";
await Factory<ToType>(fromArray).toArray(toDTO);
A secure SQL query builder for Vercel/Postgres, using parameterized queries.
- Example: Select clause:
import { withSQL } from "next-era/db";
async function get(name, createdBy) {
return await withSQL(sql)
.select({
columns: ["id", "name"],
from: "tableName",
where: {
and: {
name,
createdBy,
},
},
order: {
by: "createdDate",
sort,
},
limit: 20,
offset,
})
.execute();
}
- Example: Create clause:
import { withSQL } from "next-era/db";
async function create(data: DataDTO) {
await withSQL(sql)
.create({
into: "tableName",
values: {
name: data.name,
},
})
.execute();
}
- Example: Create multi rows clause:
import { withSQL } from "next-era/db";
async function creates(datas: DataType[]) {
await withSQL(sql)
.creates(
map(datas, (data) => ({
into: "tableName",
values: {
name: data.name,
},
})),
)
.execute();
}
- Example: Update clause:
import { withSQL } from "next-era/db";
async function update(data: DataDTO) {
await withSQL(sql)
.update({
on: "tableName",
set: {
name: data.name,
},
where: {
and: {
id: data.id,
},
},
})
.execute();
}
- Example: Delete clause:
import { withSQL } from "next-era/db";
async function _delete({ id, createdBy }) {
await withSQL(sql)
.delete({
from: "tableName",
where: {
and: {
id,
createdBy,
},
},
})
.execute();
}
- Example: Count data:
import { withSQL } from "next-era/db";
async function coundByIds({
ids,
createdBy,
}: {
ids: string;
createdBy: string;
}): Promise<number> {
const count = await withSQL(sql)
.select({
from: "tableName",
columns: "count(*)",
where: {
and: {
id: {
in: ids,
},
createdBy,
},
},
})
.execute();
return Number(count.rows[0].count);
}
- Example: Complex clause:
import { withSQL } from "next-era/db";
async function readsSameNameById({
id,
createdBy,
}: {
id: string;
createdBy: string;
}) {
const datas = await withSQL(sql)
.select({
from: "tableName",
columns: ["id", "name"],
where: {
and: {
name: {
raw: withSQL(sql)
.select({
from: "tableName2",
columns: "name",
where: { id, createdBy },
})
.toRaw(),
},
createdBy,
},
},
limit: 20,
})
.execute();
return await Factory<WordType>(words.rows).toArray(WordDTO);
}
A wrapper for handling database transactions with automatic rollback support.
- Example:
import { withTransaction } from "next-era/db";
withTransaction(sql, sql.query(query, parameterizedValues));
The Logger
class provides a structured and grouped logging system.
- Example:
import { Logger } from "next-era/log";
const { debug } = new Logger(data).groupCollapsed("Group Label");
debug`Processing...`;
debug`Completed.`.groupEnd();
Inserts a separator between array elements (e.g., React Nodes).
- Example:
import { between } from "next-era/utils";
between(
breadcrumbs.map((breadcrumb) => (
<li key={breadcrumb.label} aria-current={breadcrumb.active}>
{breadcrumb.label}
</li>
)),
(index) => (
<span key={index} className="mx-3">
/
</span>
),
);
A deep merge function that does not mutate the original object (Lodash alternative).
- Example:
import { defaultsDeep } from "next-era/utils";
defaultsDeep(...flatMap(group)).name;
Flattens an object deeply (useful for URL search parameters). This function is using for querize object params in useRouter
.
Convert from { ancestor: { parent: { brother: 'alex', sister: 'jessica' } }
to { 'ancestor.parent.brother': 'alex', 'ancestor.parent.sister': 'jessica' }
- Example:
import { flattenDeep } from "next-era/utils";
export default async function Page(props: {
params: Promise<{ id: string }>;
searchParams?: Promise<{
query?: string;
page?: string;
}>;
}) {
return (
<Form
breadcrumb={{
breadcrumbs: [
{ label: "ancestor", href: "/ancestor" },
{ label: "parent", href: "/parent" },
],
submit: {
label: "List",
type: {
button: {
href: {
path: "/ancestor/parent/list", // be generated to href: `/ancestor/parent/list/ancestor/parent/list?ancestor.parent.brother=alex&ancestor.parent.sister=jessica`
options: {
searchParams: {
ancestor: {
parent: {
brother: "alex",
sister: "jessica",
},
},
},
},
},
},
},
},
}}
/>
);
}
Restores a deeply flattened object. This function is using for dequerize object params from navigating with object params.
Convert from { 'ancestor.parent.brother': 'alex', 'ancestor.parent.sister': 'jessica' }
to { ancestor: { parent: { brother: 'alex', sister: 'jessica' } }
- Example:
import { unflattenDeep } from "next-era/utils";
export default async function Page(props: {
params: Promise<{ id: string }>;
searchParams?: Promise<{
[key: string]: string | number;
}>;
}) {
const searchParams = unflattenDeep<{ tree: TreeType }>(
await props.searchParams,
); // { ancestor: { parent: { brother: 'alex', sister: 'jessica' } }
}
Contributions are welcome! Follow these steps:
- Fork the repository: GitHub
- Create a new branch:
git checkout -b feature-branch
- Commit changes:
git commit -m "Description of changes"
- Push to branch:
git push origin feature-branch
- Open a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.