add pwa in nextjs
5
.changeset/plain-spies-smoke.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add pwa support in nextjs
|
||||||
@@ -10,7 +10,9 @@ import type { ProjectConfig } from "../types";
|
|||||||
export async function setupAddons(config: ProjectConfig) {
|
export async function setupAddons(config: ProjectConfig) {
|
||||||
const { addons, frontend, projectDir } = config;
|
const { addons, frontend, projectDir } = config;
|
||||||
const hasReactWebFrontend =
|
const hasReactWebFrontend =
|
||||||
frontend.includes("react-router") || frontend.includes("tanstack-router");
|
frontend.includes("react-router") ||
|
||||||
|
frontend.includes("tanstack-router") ||
|
||||||
|
frontend.includes("next");
|
||||||
const hasNuxtFrontend = frontend.includes("nuxt");
|
const hasNuxtFrontend = frontend.includes("nuxt");
|
||||||
const hasSvelteFrontend = frontend.includes("svelte");
|
const hasSvelteFrontend = frontend.includes("svelte");
|
||||||
const hasSolidFrontend = frontend.includes("solid");
|
const hasSolidFrontend = frontend.includes("solid");
|
||||||
|
|||||||
@@ -557,10 +557,20 @@ export async function setupAddonsTemplate(
|
|||||||
let addonDestDir = projectDir;
|
let addonDestDir = projectDir;
|
||||||
|
|
||||||
if (addon === "pwa") {
|
if (addon === "pwa") {
|
||||||
addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web");
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
addonDestDir = path.join(projectDir, "apps/web");
|
if (!(await fs.pathExists(webAppDir))) {
|
||||||
|
continue;
|
||||||
if (!(await fs.pathExists(addonDestDir))) {
|
}
|
||||||
|
addonDestDir = webAppDir;
|
||||||
|
if (context.frontend.includes("next")) {
|
||||||
|
addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/next");
|
||||||
|
} else if (
|
||||||
|
context.frontend.some((f) =>
|
||||||
|
["tanstack-router", "react-router", "solid"].includes(f),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
|
||||||
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -800,7 +800,10 @@ function processAndValidateFlags(
|
|||||||
);
|
);
|
||||||
const hasCompatibleWebFrontend = effectiveFrontend?.some((f) => {
|
const hasCompatibleWebFrontend = effectiveFrontend?.some((f) => {
|
||||||
const isPwaCompatible =
|
const isPwaCompatible =
|
||||||
f === "tanstack-router" || f === "react-router" || f === "solid";
|
f === "tanstack-router" ||
|
||||||
|
f === "react-router" ||
|
||||||
|
f === "solid" ||
|
||||||
|
f === "next";
|
||||||
const isTauriCompatible =
|
const isTauriCompatible =
|
||||||
f === "tanstack-router" ||
|
f === "tanstack-router" ||
|
||||||
f === "react-router" ||
|
f === "react-router" ||
|
||||||
@@ -828,7 +831,7 @@ function processAndValidateFlags(
|
|||||||
let incompatibleReason = "Selected frontend is not compatible.";
|
let incompatibleReason = "Selected frontend is not compatible.";
|
||||||
if (config.addons.includes("pwa")) {
|
if (config.addons.includes("pwa")) {
|
||||||
incompatibleReason =
|
incompatibleReason =
|
||||||
"PWA requires tanstack-router, react-router, or solid.";
|
"PWA requires tanstack-router, react-router, next, or solid.";
|
||||||
}
|
}
|
||||||
if (config.addons.includes("tauri")) {
|
if (config.addons.includes("tauri")) {
|
||||||
incompatibleReason =
|
incompatibleReason =
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export async function getAddonsChoice(
|
|||||||
const hasCompatiblePwaFrontend =
|
const hasCompatiblePwaFrontend =
|
||||||
frontends?.includes("react-router") ||
|
frontends?.includes("react-router") ||
|
||||||
frontends?.includes("tanstack-router") ||
|
frontends?.includes("tanstack-router") ||
|
||||||
frontends?.includes("solid");
|
frontends?.includes("solid") ||
|
||||||
|
frontends?.includes("next");
|
||||||
|
|
||||||
const hasCompatibleTauriFrontend =
|
const hasCompatibleTauriFrontend =
|
||||||
frontends?.includes("react-router") ||
|
frontends?.includes("react-router") ||
|
||||||
|
|||||||
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="92" height="92"><svg width="92" height="92" viewBox="0 0 92 92" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="8" y="8" width="76" height="76" rx="12" fill="#F5EEFF" stroke="#B79AFF" stroke-width="3"></rect>
|
||||||
|
<text x="46" y="56" text-anchor="middle" font-family="monospace" font-size="40" fill="#8F5BFF">$<tspan dx="0" dy="0">_</tspan></text>
|
||||||
|
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
|
||||||
|
@media (prefers-color-scheme: dark) { :root { filter: none; } }
|
||||||
|
</style></svg>
|
||||||
|
After Width: | Height: | Size: 616 B |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"short_name": "{{projectName}}",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
return {
|
||||||
|
name: "{{projectName}}",
|
||||||
|
short_name: "{{projectName}}",
|
||||||
|
description:
|
||||||
|
"my pwa app",
|
||||||
|
start_url: "/new",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
theme_color: "#000000",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/favicon/web-app-manifest-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/favicon/web-app-manifest-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,7 +1,5 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {};
|
||||||
output: "export"
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||