Vue H5 Template v2: three UI frameworks in dev, one in the bundle, and a README that lags the manifest
A mobile web scaffold built with Vue, based on vue-cli 4.0, Webpack 4, Vant UI, Sass, a rem-based responsive layout and a wrapped Axios client.
At a glance
- What is it?
- A Vue 3 engineering template for real mobile web work, with a type-safe API layer generated from an OpenAPI schema, a server state library kept away from the client store, and an abortable streaming chat. It is also the rare template that argues with itself in public about which linter it uses.
- Who is it for?
- Use this template if you are starting a mobile web application and want the unglamorous parts decided for you: where server state lives, how API types get generated, what the error type is, and which requests are cacheable. Its discipline is the value, particularly the rule that pages never call the HTTP client directly and the instruction to commit generated types so a contract change fails the build.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Vue, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- sunniejs/vue-h5-template3,492 ★ · +2
| Repository | 2026-09-24 | 2026-11-14 |
|---|---|---|
| sunniejs/vue-h5-template | 3.5k | 3.5k |
2 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Server state and client state are in different libraries, on purpose
The first design decision is a rule about state, and it is the reason most templates like this become unmaintainable.
Remote data, caching, retries, cancellation, mutations, pagination and infinite queries belong to a server-state library. The login session, the cart, the theme and feature flags belong to the client store. Two libraries, two jobs, and the documentation says so explicitly rather than leaving it to taste.
AI conversation state is the third case, and the project handles it separately again. A composable orchestrates the session lifecycle, and the provider transport lives in a services directory that is deliberately kept out of both the client store and the query cache. That is a defensible call: streaming conversation state has a shape neither of the other two fits, and pushing it into a global store is how you end up with a cache entry that nobody can invalidate.
The rest of the state guidance is a boundary rather than a pattern. Composable means Vue lifecycle and reactive orchestration, utilities mean pure functions with no Vue lifecycle, and services mean business logic with no framework dependency. Twelve directories, each with a one-line rule.
API types are generated, and the generated file is committed
The API layer is the part of the template with the most opinions, and the opinions are all about not letting the frontend drift from the backend.
Pages do not call the HTTP client directly, and the documentation bans the catch-all typed error parameter outright. Instead you add functions in a per-domain module directory, consume them from a page or a query composable, and narrow errors with a dedicated type guard. The client itself is typed: a response wrapper, an error type, a request identifier, a token, and explicit handling for authentication failures, timeouts and network errors.
The contract is generated. A command reads a local OpenAPI schema and writes a TypeScript declaration file. Replacing the backend means replacing the schema file or changing the input address, rerunning generation, pointing your modules at the generated types, and committing both the schema and the generated file. The reason given is the last step: CI runs the type check, so a contract change that nobody regenerated fails the build instead of failing at runtime.
The generation script is one line:
openapi-typescript openapi/schema.yaml -o src/types/api/generated.d.ts
oxfmt --write src/types/api/generated.d.tsOne detail in the manifest makes that harder to forget. The generation script does not just write the file, it formats it as part of the same command, so the generated output cannot drift into a style the formatter would reject.
Three UI frameworks ship, and the build picks one
This is the most unusual thing in the template and the most copied idea in it.
Three mobile component libraries are listed as dependencies at once, and one environment variable decides which one is real. The build tooling selects the matching resolver and the matching import alias, so writing an import the same way resolves to whichever library you chose.
The other two stay in the dependency list so you can pick one when you generate a project, but only the selected one ends up in the production bundle. The reasoning is about graph size, and it is the reason a template can offer a choice at all: the cost is at build time, not at runtime.
Each library's example page is given the same treatment rather than a placeholder: buttons, cells and lists, switches, tags or chips, and progress indicators. So switching is a real comparison rather than a guess.
The project also notes the next step: a generator command could delete the two unused dependencies at creation time instead of carrying them forever. That is the correct follow-up, and it has not happened yet.
The AI chat interface is vendor neutral and abortable
The streaming chat is the newest subsystem and the one shaped most carefully.
The provider interface is four lines: a chat method taking a readonly message array and optional options, returning an async iterable of chunks. No vendor types leak through it.
The default implementation parses a standard server-sent event stream. A project talking to a real model should proxy through its own server rather than calling the vendor from the browser, and the documentation is blunt about why: vendor keys must not sit in client-side environment variables or in browser requests.
The client side is built around one stream, one reader and one abort controller, plus regenerate and retry, Markdown rendering with code blocks, and sanitisation of rendered output. That last one is not a detail: the security baseline states that raw HTML is disabled in Markdown and everything goes through a sanitiser.
The adapter point is where this design pays off. If the backend speaks a different streaming protocol, you add another provider. The page's state machine does not change, which is the whole reason the interface is that small.
The README lists tools the manifest no longer uses
Here is the sort of drift that makes a repository worth reading.
The feature list at the top names a linter, a formatter, a styling linter, a hook tool, a staged-file tool, a commit message linter, a CI workflow and a release tool. The manifest runs a different set: the code linting is a Rust-based linter, the formatter is a different Rust-based tool, and the styling lint is the only one of the three the README's naming matches.
The same drift shows up in the tool history. The repository's own description field, which is what appears in search results, still advertises a Vue command line scaffold on webpack 4 with a rem adaptation layer. The project itself is now on Vue 3 with a modern bundler, and the version in the manifest is two, with a migration guide and a decision log.
None of this affects the template's output. All of it affects how much time you spend trusting the front page, which is why the manifest is the document to read.
The backend is two repositories, and the default mode is a mock
The companion backend used to live in this repository and no longer does, which is the change that most affects a new project.
There are now two separate repositories: one Go service handling authentication, users, the delivery projects, product business and the database, and one Python service handling the model provider, the event stream and rate limiting. The documentation even suggests names for both. A parent directory exists only to run the two together locally under compose.
The default development command does not need any of it. The mock is on by default, login accepts any non-empty credentials, and the mock server streams the chat response chunk by chunk from a single endpoint. The routes are laid out so you can walk the whole surface without a server: a shop, a cart, a request example that deliberately includes four hundred level errors, a conflict, a business validation error, a server error and a timeout, and a workspace page with full create, read, update and delete in both mock and real modes.
The integration mode is the other half: one compose command for the database and the two services, then a development command that reads a committed integration environment file. There is even a smoke script on the Python side that walks the whole path from the frontend proxy through the business API and the database to the event stream.
Five committed environment files and a roadmap in three tiers
The repository structure has a few decisions worth copying.
Five environment files are committed, one per mode, plus the example. For a template that tells you to copy the example to a local file, that means every mode's variable set is visible in the repository and the differences between modes are reviewable.
The container image is three instructions: a web server base image, the built output copied into its document root, and the project's own server configuration copied over the image's default. No runtime Node, no multi-stage build in the shipped artefact.
The security baseline is unusually specific for a template: named browser targets rather than a blanket compatibility promise, no wholesale polyfill bundle, redirects restricted to in-site absolute paths, the mock off by default in production, the sample token in session storage, and a recommendation for real projects to move to cookies or a backend-for-frontend with a content security policy, cross-site request forgery protection and rate limiting at the gateway.
And the roadmap is sorted by whether something is implemented, recommended when your business needs it, or optional and dependent on your deployment. That last tier explicitly says those items should not become template defaults, which is the sentence that keeps a template from turning into a framework.
Editorial conclusion
Use this template if you are starting a mobile web application and want the unglamorous parts decided for you: where server state lives, how API types get generated, what the error type is, and which requests are cacheable. Its discipline is the value, particularly the rule that pages never call the HTTP client directly and the instruction to commit generated types so a contract change fails the build. Skip it if you want a component library to browse, because the project is explicit that it is not one, and skip it if you only need a single page. Before you start, read the manifest rather than the feature list, because the two disagree about which linter and which formatter run, and decide early whether you want the companion backend at all, since the default development mode is a mock and the backend is two separate repositories.
Frequently asked questions
What is the Vue H5 Template for?
It is a Vue 3 engineering template for real mobile web work, and the README is explicit that it is not a component library playground. It ships a type-safe API layer generated from an OpenAPI schema, server state in one library and client state in another, an abortable streaming chat, an SVG sprite, optional PWA, production image optimisation and a test baseline.
How do I run the vue-h5-template project locally?
You need a recent Node and pnpm, then install dependencies, copy the example environment file to a local one, and start the development server. The mock is enabled by default, so no backend is required, and login accepts any non-empty username and password.
How does vue-h5-template handle API type safety?
A command generates TypeScript declarations from a local OpenAPI schema, and both the schema and the generated file are committed so a CI type check catches contract drift. Pages do not call the HTTP client directly, and errors are narrowed with a dedicated type guard rather than a catch-all typed parameter.
Does vue-h5-template include a backend?
Not in the frontend repository any more. The backend was split into two separate repositories, one Go service for authentication, users, the delivery projects, products and the database, and one Python service for the model provider, the event stream and rate limiting. The default development mode still runs against the frontend mock.
Why does vue-h5-template ship three UI component libraries?
One environment variable selects between them at build time, and the build picks the matching resolver and import alias so only the selected library reaches the production bundle. The other two remain as dependencies for choosing at project creation, with a note that a generator command could remove them.
What are the browser and security targets of vue-h5-template?
Recent Safari on iOS, recent Chrome on Android and modern embedded web views in messaging apps, with no wholesale polyfill bundle for old browsers. Markdown output is sanitised and raw HTML is disabled, redirects only accept in-site absolute paths, the mock is off in production, and real projects are pointed at cookies or a backend-for-frontend with a content security policy and rate limiting.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sunniejs-vue-h5-template)Star history chart for your README
[](https://hysenlabs.com/projects/sunniejs-vue-h5-template)Was this useful?