import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Page, Layout, Card, Text } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
// authenticate.admin verifică automat token-ul de sesiune în iframe
const { admin, session } = await authenticate.admin(request);
// Aduci doar datele esențiale pentru prima randare
const response = await admin.graphql(
`#graphql
query getShopInfo {
shop {
name
primaryDomain {
url
}
}
}`
);
const responseJson = await response.json();
return json({ shop: responseJson.data.shop });
};
export default function Dashboard() {
const { shop } = useLoaderData<typeof loader>();
return (
<Page title="Dashboard">
<Layout>
<Layout.Section>
<Card>
<Text as="h2" variant="headingMd">
Conectat la magazinul: {shop.name}
</Text>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}Dacă vrei să urci o aplicație în Shopify App Store, combinația oficială Polaris + Remix e cel mai rapid drum, dar vine la pachet cu niște dureri de cap specifice. Am trecut prin asta acum câteva luni cu o aplicație de sync pentru stocuri și am învățat pe pielea mea ce trece și ce pică la review-ul lor tehnic. Îți arăt exact unde te blokezi și cum să fentezi respingerile inutile.
De ce Remix + Polaris și unde e capcana
Shopify a împins Remix ca default-ul lor de ceva timp. E logic: încarcă datele pe server, are loaders și actions curate, iar Polaris (sistemul lor de design) se integrează nativ cu App Bridge. Toate bune și frumoase pe hârtie.
În realitate, trade-off-ul e destul de dur. Polaris e excelent dacă vrei ca aplicația ta să arate exact ca o extensie nativă din admin-ul Shopify (și asta îți garantează puncte bonus la review). Dar dacă clientul tău vrea un branding custom, cu culori nebune și layout-uri atipice, Polaris devine un coșmar de suprascris. CSS-ul lor e încăpățânat și te vei bate cu specificitatea selectorilor la fiecare pas. Pentru design custom, mai bine folosești Tailwind direct și îți asumi că scrii mai mult cod pentru UI.
La un proiect cu 1.200 de instalări active în prima lună, am ales Polaris pur și simplu pentru viteză. Am vrut să trecem de review din prima, fără discuții pe UX.
Setup-ul de care nu scrie în documentația oficială
Când generezi app-ul cu generatorul lor CLI, primești un boilerplate destul de stufos. Sfatul meu e să cureți imediat paginile de demo, dar nu te atinge de config-ul de session storage. Shopify folosește acum token-uri bazate pe sesiune transmise prin headere ca să evite problemele cu cookie-urile third-party din Safari. Dacă strici ceva acolo, aplicația ta nu se va mai încărca deloc în iframe.
Am pățit la prima versiune să am un delay de 2 secunde la încărcarea paginii pentru că făceam interogări greșite în loader. Remix te lasă să muți toată logica grea pe server, așa că folosește loader-ul doar pentru datele critice de care are nevoie Polaris ca să randeze scheletul paginii.
Review-ul Shopify: De ce o să-ți iei reject din prima
Review-ul Shopify nu mai e făcut doar de oameni care dau click-uri manual. Au boți automați care testează performanța, securitatea și flow-ul de instalare.
Am luat reject de două ori înainte să fim aprobați. Prima dată: pentru că nu foloseam App Bridge corect la navigare și se reîncărca tot iframe-ul (ceea ce distrugea timpii de încărcare). A doua oară: pentru că nu am gestionat corect starea de reinstalare. Când un user șterge aplicația și o reinstalează în aceeași zi, token-ul vechi din baza ta de date e invalid, dar sesiunea încă poate părea activă în codul tău dacă nu verifici webhook-ul de dezinstalare.
Asigură-te că asculți de webhook-ul APP_UNINSTALLED și ștergi imediat token-ul din baza de date. Altfel, testerul Shopify va încerca o reinstalare și aplicația ta va crăpa într-o buclă infinită de redirect-uri, lucru care înseamnă reject instant.
Voi ce folosiți pentru aplicațiile de Shopify? Mergeți pe stack-ul oficial cu Remix sau preferați un setup custom cu Next.js și baze de date separate?