create-nextblock 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/nextblock-template/README.md +63 -132
- package/templates/nextblock-template/app/layout.tsx +26 -15
- package/templates/nextblock-template/eslint.config.mjs +0 -2
- package/templates/nextblock-template/next-env.d.ts +1 -1
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/public/favicon/android-chrome-192x192.png +0 -0
- package/templates/nextblock-template/public/favicon/android-chrome-512x512.png +0 -0
- package/templates/nextblock-template/public/favicon/apple-touch-icon.png +0 -0
- package/templates/nextblock-template/public/favicon/favicon-16x16.png +0 -0
- package/templates/nextblock-template/public/favicon/favicon-32x32.png +0 -0
- package/templates/nextblock-template/public/favicon/favicon.ico +0 -0
- package/templates/nextblock-template/public/favicon/site.webmanifest +1 -0
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
# Next.js
|
|
1
|
+
# Next.js 16 & Supabase - Ultra-Fast CMS Template
|
|
2
2
|
|
|
3
|
-
This project is a starter template for building an ultra-fast, localized, block-based Content Management System (CMS) using Next.js
|
|
3
|
+
This project is a starter template for building an ultra-fast, localized, block-based Content Management System (CMS) using Next.js 16 (App Router), Supabase for the backend (PostgreSQL, Auth, Storage via R2), Tailwind CSS for styling, and shadcn/ui for components.
|
|
4
4
|
|
|
5
5
|
It features:
|
|
6
|
+
|
|
6
7
|
- Role-Based Access Control (Admin, Writer, User)
|
|
7
8
|
- Internationalization (i18n) with client-side language switching on single URLs
|
|
8
9
|
- Block-based content editor for Pages and Posts
|
|
@@ -12,156 +13,84 @@ It features:
|
|
|
12
13
|
|
|
13
14
|
## Features Implemented (Phases 1-6)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
- **Authentication & Authorization (Phase 1):** User roles (ADMIN, WRITER, USER), profiles table linked to `auth.users`, Row Level Security (RLS) on tables, Next.js middleware for route protection, and client-side auth context.
|
|
17
|
+
- **Internationalization (Phase 2):** `languages` table in Supabase, client-side language switching using `LanguageContext` without URL path changes (e.g., `/about-us` serves content based on selected language), and auto-creation of localized placeholder content.
|
|
18
|
+
- **CMS Schema & Core CRUD (Phase 3):** Database tables for `pages`, `posts`, `media`, `blocks`, `navigation_items`. CRUD UIs and server actions for managing Pages, Posts, Navigation Items, Users (role changes), and Languages.
|
|
19
|
+
- **Block-Based Content Builder (Phase 4):** Dynamic block system for Pages (and Posts), UI for adding, editing (basic forms), deleting, and drag-and-drop reordering of content blocks.
|
|
20
|
+
- **Rich Text & Media (Phase 5):** Tiptap rich text editor integrated into "Text" blocks, image insertion from Media Library into Tiptap, and media uploads to Cloudflare R2 with a Media Library UI (upload, view, delete, edit metadata).
|
|
21
|
+
- **SSG & Revalidation (Phase 6):** Static generation of public pages/posts (default language), client-side content fetching for language changes, `generateStaticParams`, `generateMetadata`, and on-demand revalidation via Supabase Database Webhooks calling a Next.js API route.
|
|
22
|
+
|
|
23
|
+
## 🚀 Getting Started
|
|
24
|
+
|
|
25
|
+
The fastest way to start a new project is with our interactive setup wizard.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm create nextblock@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This command will:
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
1. Scaffold a new NextBlock project.
|
|
34
|
+
2. Guide you through Supabase and Cloudflare R2 configuration.
|
|
35
|
+
3. Set up your local environment variables automatically.
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
### Manual Setup
|
|
38
|
+
|
|
39
|
+
If you are contributing to the core repo or prefer manual setup:
|
|
40
|
+
|
|
41
|
+
1. **Clone the repo:**
|
|
26
42
|
|
|
27
|
-
2. **Clone This Repository:**
|
|
28
43
|
```bash
|
|
29
|
-
git clone
|
|
30
|
-
cd
|
|
44
|
+
git clone https://github.com/Webman-Dev/nextblock-monorepo.git
|
|
45
|
+
cd nextblock-monorepo
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
2. **Install dependencies:**
|
|
49
|
+
|
|
34
50
|
```bash
|
|
35
51
|
npm install
|
|
36
|
-
# or
|
|
37
|
-
yarn install
|
|
38
|
-
# or
|
|
39
|
-
pnpm install
|
|
40
52
|
```
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# Supabase Project Connection (from your Supabase project's API settings)
|
|
48
|
-
NEXT_PUBLIC_SUPABASE_URL=[https://your-project-ref.supabase.co](https://your-project-ref.supabase.co)
|
|
49
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-public-anon-key
|
|
50
|
-
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # Found in API settings, needed for admin actions like deleting users
|
|
51
|
-
SUPABASE_PROJECT_ID=your-supabase-project-id # Used by the Supabase CLI (e.g., in supabase/config.toml)
|
|
52
|
-
|
|
53
|
-
# Cloudflare R2 Storage (from your Cloudflare R2 bucket settings & API token)
|
|
54
|
-
NEXT_PUBLIC_R2_BASE_URL=[https://your-r2-public-url.r2.dev/your-bucket-name](https://your-r2-public-url.r2.dev/your-bucket-name) # Or your custom domain for R2
|
|
55
|
-
R2_ACCOUNT_ID=your_cloudflare_account_id
|
|
56
|
-
R2_ACCESS_KEY_ID=your_r2_access_key_id
|
|
57
|
-
R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
|
|
58
|
-
R2_BUCKET_NAME=your_r2_bucket_name
|
|
59
|
-
R2_S3_ENDPOINT=https://<R2_ACCOUNT_ID>.r2.cloudflarestorage.com # e.g., [https://abcdef12345.r2.cloudflarestorage.com](https://abcdef12345.r2.cloudflarestorage.com)
|
|
60
|
-
R2_REGION=auto # Typically 'auto' for R2
|
|
61
|
-
|
|
62
|
-
# Next.js Site Configuration
|
|
63
|
-
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # For local dev; update for production (e.g., [https://www.yourdomain.com](https://www.yourdomain.com))
|
|
64
|
-
REVALIDATE_SECRET_TOKEN=generate_a_strong_random_string_here # Used to secure the on-demand revalidation API endpoint
|
|
65
|
-
```
|
|
66
|
-
* `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` can be found in your Supabase project's API settings.
|
|
67
|
-
* `SUPABASE_SERVICE_ROLE_KEY` is also in API settings (typically hidden by default, click "Reveal").
|
|
68
|
-
* Generate a strong, unique string for `REVALIDATE_SECRET_TOKEN`.
|
|
69
|
-
|
|
70
|
-
5. **Apply Supabase Migrations:**
|
|
71
|
-
* Ensure you have the Supabase CLI installed and are logged in (`supabase login`).
|
|
72
|
-
* Link your local project to your Supabase project:
|
|
73
|
-
```bash
|
|
74
|
-
supabase link --project-ref your-project-ref
|
|
75
|
-
```
|
|
76
|
-
* Apply all database migrations:
|
|
77
|
-
```bash
|
|
78
|
-
supabase db push
|
|
79
|
-
```
|
|
80
|
-
Alternatively, if you prefer to run migrations individually (e.g., for a fresh setup or to ensure order):
|
|
81
|
-
```bash
|
|
82
|
-
supabase migration up
|
|
83
|
-
```
|
|
84
|
-
* This will create all necessary tables (`profiles`, `languages`, `pages`, `posts`, `media`, `blocks`, `navigation_items`), roles, RLS policies, and helper functions.
|
|
85
|
-
|
|
86
|
-
6. **Configure Supabase Database Webhooks for On-Demand Revalidation:**
|
|
87
|
-
Since this project avoids using Supabase Edge Functions (and their Docker dependency) for revalidation, you need to manually set up Database Webhooks to call your Next.js API endpoint directly.
|
|
88
|
-
|
|
89
|
-
* Go to your Supabase Project Dashboard -> Database -> Webhooks.
|
|
90
|
-
* Click "Create a new webhook".
|
|
91
|
-
|
|
92
|
-
* **For the `pages` Table:**
|
|
93
|
-
* **Name:** `Next.js Revalidate Pages` (or similar)
|
|
94
|
-
* **Table:** Select `pages` (from the `public` schema).
|
|
95
|
-
* **Events:** Check `INSERT`, `UPDATE`, `DELETE`.
|
|
96
|
-
* **Webhook Type:** `HTTP Request`
|
|
97
|
-
* **HTTP URL:** Your Next.js application's revalidation API endpoint.
|
|
98
|
-
* For local development (if using a tunneling service like ngrok to expose localhost): `http://your-ngrok-url.ngrok.io/api/revalidate`
|
|
99
|
-
* For production (e.g., Vercel): `https://your-app-name.vercel.app/api/revalidate`
|
|
100
|
-
* **HTTP Method:** `POST`
|
|
101
|
-
* **HTTP Headers:**
|
|
102
|
-
* Click "Add header".
|
|
103
|
-
* Header name: `x-revalidate-secret`
|
|
104
|
-
* Header value: The same `REVALIDATE_SECRET_TOKEN` you set in your `.env.local`.
|
|
105
|
-
* Click "Add header" again.
|
|
106
|
-
* Header name: `Content-Type`
|
|
107
|
-
* Header value: `application/json`
|
|
108
|
-
* Click "Create webhook".
|
|
109
|
-
|
|
110
|
-
* **For the `posts` Table:**
|
|
111
|
-
* Create another webhook with similar settings:
|
|
112
|
-
* **Name:** `Next.js Revalidate Posts`
|
|
113
|
-
* **Table:** `posts`
|
|
114
|
-
* **Events:** `INSERT`, `UPDATE`, `DELETE`.
|
|
115
|
-
* **HTTP URL:** Same as above.
|
|
116
|
-
* **HTTP Method:** `POST`
|
|
117
|
-
* **HTTP Headers:**
|
|
118
|
-
* `x-revalidate-secret`: Your `REVALIDATE_SECRET_TOKEN`
|
|
119
|
-
* `Content-Type`: `application/json`
|
|
120
|
-
* Click "Create webhook".
|
|
121
|
-
|
|
122
|
-
7. **Run the Next.js Development Server:**
|
|
54
|
+
3. **Setup Environment:**
|
|
55
|
+
Copy `.env.exemple` to `apps/nextblock/.env.local` and fill in your Supabase/R2 credentials.
|
|
56
|
+
|
|
57
|
+
4. **Run the Dev Server:**
|
|
58
|
+
|
|
123
59
|
```bash
|
|
124
|
-
|
|
125
|
-
# or
|
|
126
|
-
yarn dev
|
|
127
|
-
# or
|
|
128
|
-
pnpm dev
|
|
60
|
+
nx serve nextblock
|
|
129
61
|
```
|
|
130
|
-
The application should now be running on [http://localhost:3000](http://localhost:3000/).
|
|
131
62
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* Find your user's row (by their ID, which matches `auth.users.id`).
|
|
136
|
-
* Change the `role` column value from `USER` to `ADMIN`.
|
|
63
|
+
5. **Initial Admin User Setup:**
|
|
64
|
+
- Sign up for a new user account through the application's sign-up page.
|
|
65
|
+
- After signing up and verifying the email, you'll need to manually update this user's role to `ADMIN` in the Supabase `profiles` table.
|
|
137
66
|
|
|
138
|
-
|
|
139
|
-
|
|
67
|
+
6. **Shadcn/UI Styling (Optional):**
|
|
68
|
+
- This template comes with the default shadcn/ui style initialized. If you want to customize the theme or use a different base color, you can delete `components.json` and re-initialize shadcn/ui following their [official documentation](https://ui.shadcn.com/docs/installation/next).
|
|
140
69
|
|
|
141
70
|
## Project Structure Highlights
|
|
142
71
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
72
|
+
- `app/`: Next.js App Router.
|
|
73
|
+
- `app/(auth-pages)/`: Routes for sign-in, sign-up, etc.
|
|
74
|
+
- `app/cms/`: CMS admin panel routes and layouts.
|
|
75
|
+
- `app/cms/[entity]/`: CRUD pages for different content types (pages, posts, media, users, navigation, languages).
|
|
76
|
+
- `app/cms/blocks/`: Components and actions related to the block editor.
|
|
77
|
+
- `app/[slug]/`: Dynamic route for public "Pages".
|
|
78
|
+
- `app/article/[slug]/`: Dynamic route for public "Articles".
|
|
79
|
+
- `app/api/`: API routes (e.g., for revalidation, R2 pre-signed URLs).
|
|
80
|
+
- `components/`: Shared UI components (shadcn/ui based).
|
|
81
|
+
- `components/ui/`: shadcn/ui components.
|
|
82
|
+
- `context/`: React Context providers (e.g., `AuthContext`, `LanguageContext`).
|
|
83
|
+
- `lib/`: Utility functions and configurations.
|
|
84
|
+
- `lib/cloudflare/`: Client for Cloudflare R2.
|
|
85
|
+
- `utils/supabase/`: Supabase client setup, types, and middleware helpers.
|
|
86
|
+
- `supabase/migrations/`: SQL database migrations.
|
|
158
87
|
|
|
159
88
|
## Documentation
|
|
160
89
|
|
|
161
90
|
For a deeper understanding of the CMS's internal workings, please refer to the detailed documentation:
|
|
162
91
|
|
|
163
|
-
|
|
164
|
-
|
|
92
|
+
- **[CMS Application Overview](./docs/cms-application-overview.md):** A high-level guide to the Next.js application structure, core modules, and key functionalities.
|
|
93
|
+
- **[Block Editor Architecture](./docs/cms-architecture-overview.md):** A technical deep-dive into the architecture of the block-based content editor.
|
|
165
94
|
|
|
166
95
|
## Deployment
|
|
167
96
|
|
|
@@ -170,7 +99,7 @@ This project is optimized for deployment on [Vercel](https://vercel.com/).
|
|
|
170
99
|
1. Push your code to a GitHub/GitLab/Bitbucket repository.
|
|
171
100
|
2. Import the project into Vercel.
|
|
172
101
|
3. **Configure Environment Variables in Vercel:**
|
|
173
|
-
|
|
102
|
+
- Add all the environment variables from your `.env.local` file to your Vercel project settings (Project Settings -> Environment Variables). This includes Supabase keys, R2 keys, `NEXT_PUBLIC_SITE_URL` (set to your production domain), and `REVALIDATE_SECRET_TOKEN`.
|
|
174
103
|
4. Vercel will automatically build and deploy your Next.js application.
|
|
175
104
|
5. Ensure your Supabase Database Webhooks are pointing to your production Next.js API endpoint for revalidation.
|
|
176
105
|
|
|
@@ -179,7 +108,8 @@ This project is optimized for deployment on [Vercel](https://vercel.com/).
|
|
|
179
108
|
This project includes a simple script to backup your Supabase PostgreSQL database.
|
|
180
109
|
|
|
181
110
|
**Requirements:**
|
|
182
|
-
|
|
111
|
+
|
|
112
|
+
- You must have the PostgreSQL command-line tools (`pg_dump`) installed and available in your system's PATH.
|
|
183
113
|
|
|
184
114
|
**Usage:**
|
|
185
115
|
To create a backup, run the following command from your project root:
|
|
@@ -189,6 +119,7 @@ npm run db:backup
|
|
|
189
119
|
```
|
|
190
120
|
|
|
191
121
|
This command will generate a timestamped SQL dump file and save it to the `backup/` directory.
|
|
122
|
+
|
|
192
123
|
## Feedback and Issues
|
|
193
124
|
|
|
194
125
|
Please file feedback and issues on the GitHub repository for this project.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import '@nextblock-cms/ui/styles/globals.css';
|
|
2
|
-
import '@nextblock-cms/editor/styles/editor.css';
|
|
3
|
-
// app/layout.tsx
|
|
1
|
+
import '@nextblock-cms/ui/styles/globals.css';
|
|
2
|
+
import '@nextblock-cms/editor/styles/editor.css';
|
|
3
|
+
// app/layout.tsx
|
|
4
4
|
import { EnvVarWarning } from "@/components/env-var-warning";
|
|
5
5
|
import { ThemeSwitcher } from '@/components/theme-switcher';
|
|
6
6
|
import type { Metadata } from 'next';
|
|
@@ -13,8 +13,8 @@ import { getActiveLanguagesServerSide } from '@nextblock-cms/db/server';
|
|
|
13
13
|
import { getNavigationMenu } from '@/app/cms/navigation/actions';
|
|
14
14
|
import { getActiveLogo } from '@/app/cms/settings/logos/actions';
|
|
15
15
|
import { getCopyrightSettings } from '@/app/cms/settings/copyright/actions';
|
|
16
|
-
import { getTranslations } from '@/app/cms/settings/extra-translations/actions';
|
|
17
|
-
import type { Database } from '@nextblock-cms/db';
|
|
16
|
+
import { getTranslations } from '@/app/cms/settings/extra-translations/actions';
|
|
17
|
+
import type { Database } from '@nextblock-cms/db';
|
|
18
18
|
import { headers, cookies } from 'next/headers';
|
|
19
19
|
|
|
20
20
|
const defaultUrl = process.env.VERCEL_URL
|
|
@@ -49,7 +49,7 @@ async function loadLayoutData() {
|
|
|
49
49
|
] = await Promise.all([
|
|
50
50
|
supabase.auth.getUser(),
|
|
51
51
|
getActiveLanguagesServerSide().catch(() => []),
|
|
52
|
-
getCopyrightSettings().catch(() => ({ en: '© {year} Nextblock CMS. All rights reserved.' })),
|
|
52
|
+
getCopyrightSettings().catch(() => ({ en: '© {year} Nextblock CMS. All rights reserved.' })),
|
|
53
53
|
getTranslations().catch(() => []),
|
|
54
54
|
]);
|
|
55
55
|
|
|
@@ -65,8 +65,8 @@ async function loadLayoutData() {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const copyrightSettings = copyrightSettingsResult as Record<string, string>;
|
|
68
|
-
const fallbackTemplate =
|
|
69
|
-
copyrightSettings['en'] ?? '© {year} Nextblock CMS. All rights reserved.';
|
|
68
|
+
const fallbackTemplate =
|
|
69
|
+
copyrightSettings['en'] ?? '© {year} Nextblock CMS. All rights reserved.';
|
|
70
70
|
const templateForLocale =
|
|
71
71
|
copyrightSettings[serverDeterminedLocale] ?? fallbackTemplate;
|
|
72
72
|
const copyrightText = templateForLocale.replace('{year}', new Date().getFullYear().toString());
|
|
@@ -78,11 +78,11 @@ async function loadLayoutData() {
|
|
|
78
78
|
|
|
79
79
|
const headerNavItems: NavigationItem[] = await getNavigationMenu('HEADER', serverDeterminedLocale).catch(() => []);
|
|
80
80
|
const footerNavItems: NavigationItem[] = await getNavigationMenu('FOOTER', serverDeterminedLocale).catch(() => []);
|
|
81
|
-
const logo = await getActiveLogo().catch(() => null);
|
|
81
|
+
const logo = await getActiveLogo().catch(() => null);
|
|
82
82
|
|
|
83
83
|
const role = profile?.role ?? null;
|
|
84
84
|
const canAccessCms = role === 'ADMIN' || role === 'WRITER';
|
|
85
|
-
const siteTitle = logo?.site_title ?? 'Nextblock';
|
|
85
|
+
const siteTitle = logo?.site_title ?? 'Nextblock';
|
|
86
86
|
|
|
87
87
|
return {
|
|
88
88
|
user,
|
|
@@ -102,11 +102,22 @@ async function loadLayoutData() {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
export const metadata: Metadata = {
|
|
106
|
-
metadataBase: new URL(defaultUrl),
|
|
107
|
-
title: 'Nextblock CMS',
|
|
108
|
-
description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
|
|
109
|
-
|
|
105
|
+
export const metadata: Metadata = {
|
|
106
|
+
metadataBase: new URL(defaultUrl),
|
|
107
|
+
title: 'Nextblock CMS',
|
|
108
|
+
description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
|
|
109
|
+
icons: {
|
|
110
|
+
icon: [
|
|
111
|
+
{ url: '/favicon/favicon.ico' },
|
|
112
|
+
{ url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
|
|
113
|
+
{ url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
|
|
114
|
+
],
|
|
115
|
+
apple: [
|
|
116
|
+
{ url: '/favicon/apple-touch-icon.png' },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
manifest: '/favicon/site.webmanifest',
|
|
120
|
+
};
|
|
110
121
|
|
|
111
122
|
export default async function RootLayout({
|
|
112
123
|
children,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import nx from '@nx/eslint-plugin';
|
|
2
|
-
import baseConfig from '../../eslint.config.mjs';
|
|
3
2
|
import nextPlugin from '@next/eslint-plugin-next';
|
|
4
3
|
|
|
5
4
|
const nextRules = {
|
|
@@ -8,7 +7,6 @@ const nextRules = {
|
|
|
8
7
|
};
|
|
9
8
|
|
|
10
9
|
const config = [
|
|
11
|
-
...baseConfig,
|
|
12
10
|
...nx.configs['flat/react-typescript'],
|
|
13
11
|
{
|
|
14
12
|
ignores: ['.next/**/*', '**/next-env.d.ts', 'apps/nextblock/next-env.d.ts'],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|