A framework agnostic Static Site Generator (SSG) using Vite.
Common server side rendering (SSR) like Next.js, Nuxt.js, and others are great, but they are tied to their respective, often proprietary, frameworks. This project aims to provide a framework agnostic SSG that can be used with any framework that can be rendered to HTML.
Serverless and static hosting options like GitHub Pages, GoogleSites, and others lack the ability to serve your multi-page app with route specific metadata. This project aims to provide a solution to that problem by generating static HTML files that are optimized for search engines.
This project is built on top of Vite, a fast build tool that provides a great developer experience. It leverages Vite's fast build times and SSR capabilities to generate static HTML files that are optimized for search engines.
- Framework agnostic: Works with any framework that can be rendered to HTML.
- Fast: Built on top of Vite, it leverages Vite's fast build times and Server Side Rendering (SSR).
- SEO friendly: Generates static HTML files that are optimized for search engines.
- Easy to use: Simple API and configuration.
- Customizable: Easily extendable with plugins and custom configurations.
- Fetches the RSS feed: The script fetches the RSS feed from the specified URL and parses it to extract the items.
- Preloads the data: The script preloads the data needed for rendering the app. This is done by dispatching the data to your store.
- Copies the build index.html: The script copies the build index.html file at the destination directory, and uses that as a base to generate the static HTML files.
- Renders the app: The script renders the app using the
renderfunction. This function takes a path as an argument and returns the rendered HTML string. - Merges the metadata: The script renders the metadata using the
renderMetadatafunction, and merges it with existing metadata from the index.html file. - Writes the static HTML files: The script writes the resulting content to new static HTML files at the destination directory.
To get started, you need to install the package and its dependencies. You can do this using npm or yarn.
npm install -D @accessitech/vite-ssg tsx
# or
yarn add -D @accessitech/vite-ssg tsxFor TypeScript projects that want to work directly with the source TypeScript files (for better debugging, source maps, and development experience), this package exposes its TypeScript source files through the exports.typescript field.
Modern TypeScript tools and bundlers (like Vite, webpack 5+, etc.) will automatically prefer the TypeScript source when available. This allows you to:
- Get better source maps that point to the original TypeScript files
- Debug directly in the source TypeScript code
- Get full IntelliSense and type information
- Have your bundler handle the compilation optimally
To use JavaScript config files in ES module projects (which is recommended for compatibility), rename your config file:
# Instead of ssg.config.ts, use:
mv ssg.config.ts ssg.config.jsTo use the package, you need to create a ssg.config.ts file in the root of your project. This file will contain the configuration for the SSG.
// ssg.config.ts
import { ConfigProps, defineConfig } from "@accessitech/vite-ssg";
export const config: ConfigProps = defineConfig({
// ...
});The main script will use your server file specified by config.ssrEntry in the ssg.config.ts file. This file should export:
- a
renderfunction that takes a path as an argument and returns the rendered HTML string. - a
renderMetadatafunction that takes a data object as an argument and returns the rendered metadata string. - a
preloadfunction loads your data and dispatches it to your store. - a
fetchMetaDatafunction that takes a URL as an argument and returns the associated metadata and file content. - any other business logic you need to fetch and parse your metadata.
IMPORTANT: ReactDOMServer is imported dynamically to avoid SSR issues with Vite.
// src/server.tsx
import App from './App/App';
import { YourDataProps, YourMetadataComponent } from './Your/MetaData/Component';
export const preload = async (data:any) => {
// fetch data needed for rendering
// customize as needed to populate your store
store.dispatch({ type: 'YOUR_ACTION', payload: data });
};
export const render = async (path:string):string => {
// customize as needed to render your app
const ReactDOMServer = (await import('react-dom/server')).default;
return ReactDOMServer.renderToString(<App path={path} />);
};
export const renderMetadata = async (data:YourDataProps):string => {
// customize as needed to render your metadata
const ReactDOMServer = (await import('react-dom/server')).default;
return ReactDOMServer.renderToString(<YourMetadataComponent {...data} />);
};
export const fetchMetaData = async (url:string):
Promise<{ metaData: { [key: string]: string }, fileContent: string }>
=> {
// fetch your metadata from the url
const fileContent:string = await businessLogicToGetFileContent(url);
const metaData: {[key:string]: string} = await businessLogicToParseMetaData(fileContent);
// IMPORTANT: The function must return an object with a 'metaData' property
// The metaData object should contain standard metadata fields like:
// - title: Page title
// - description: Page description
// - url: Canonical URL (optional, will be auto-generated if not provided)
// - image: Social media image URL
// - type: Open Graph type (article, website, etc.)
return { metaData, fileContent };
};Add a script to your package.json file to run the SSG. This script will use the tsx command to run the generate function from the package.
{
...
"scripts": {
...
"ssg": "tsx ./node_modules/@accessitech/vite-ssg/generate"
}
...
}To run the SSG, you can use the following command:
npm run ssg
# or
yarn ssgConfiguration can be done in a ssg.config.ts, ssg.config.js, or ssg.config.json file, or by passing a ConfigProps object directly to the generate function. Configuration files should export a ConfigProps object.
// @accessitech/vite-ssg/index.ts
import { UserConfig, ServerOptions } from "vite";
...
// ViteServerProps is a subset of UserConfig from Vite
export interface ViteServerProps extends UserConfig {}
// ConfigProps is the configuration API for @accessitech/vite-ssg
export interface ConfigProps {
// The entry server file containing the render function
// Note: Don't include the leading slash
ssrEntry: string;
// The source URL for the RSS feed
// Note: Don't include the leading
urlSrc: string;
// The destination directory for the generated static files
// This should be your build folder
// Note: Don't include the leading or trailing slash
dest: string;
// The paths that don't require preloading data
// Note: DO use the leading slash
// and DO NOT use the trailing slash
// e.g. "/blog" instead of "blog" or "/blog/"
staticPaths: string[];
// The metadata associated with the static paths
// Note: DO use the leading slash
// and DO NOT use the trailing slash
staticMetaData: string[];
// A function that takes an array of items and returns an array of paths
// Note: DO use the leading slash
// and DO NOT use the trailing slash
pathsBuilder: (items: any[]) => string[];
// The base URL for the production environment
// Note: don't include the trailing slash
productionUrlBase: string;
// The Vite server configuration
viteServer: ViteServerProps;
// Whether to replace the index.html file in the destination directory
// with the generated static index.html file
replaceIndexHtml?: boolean;
}
// The default configuration file
export const CONFIG: ConfigProps = {
ssrEntry: "src/server.tsx",
urlSrc: "public/rss.xml",
dest: "docs",
staticPaths: ["/", "/blog"],
staticMetaData: ["src/App/meta.ts", "src/pages/Blog/meta.ts"],
productionUrlBase: "https://accessi.tech",
pathsBuilder: (items) =>
items.map((item) => {
const { link } = item;
const id = link.split("/").pop()?.replace(".md", "") || "";
return `/blog/${id}`;
}),
viteServer: {
root: path.resolve(process.cwd()),
plugins: [reactPlugin()], // Add your Vite plugins here
server: { middlewareMode: true, port: 3000, ssr: true } as ServerOptions,
appType: "custom",
},
};This project is licensed under the MIT License. See the LICENSE file for details.